<!--
  ~ Copyright 2019. the original author or authors.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 1.5.6.1">
<meta name="author" content="v2.0.0-beta2">
<title>Dew:一站式微服务解决方案 [2.x Building&#8230;&#8203;]</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
<style>
/* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */
/* Remove comment around @import statement below when using as a custom stylesheet */
/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700";*/
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}
audio,canvas,video{display:inline-block}
audio:not([controls]){display:none;height:0}
[hidden],template{display:none}
script{display:none!important}
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
a{background:transparent}
a:focus{outline:thin dotted}
a:active,a:hover{outline:0}
h1{font-size:2em;margin:.67em 0}
abbr[title]{border-bottom:1px dotted}
b,strong{font-weight:bold}
dfn{font-style:italic}
hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}
mark{background:#ff0;color:#000}
code,kbd,pre,samp{font-family:monospace;font-size:1em}
pre{white-space:pre-wrap}
q{quotes:"\201C" "\201D" "\2018" "\2019"}
small{font-size:80%}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sup{top:-.5em}
sub{bottom:-.25em}
img{border:0}
svg:not(:root){overflow:hidden}
figure{margin:0}
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
legend{border:0;padding:0}
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
button,input{line-height:normal}
button,select{text-transform:none}
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
button[disabled],html input[disabled]{cursor:default}
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}
input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}
input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
textarea{overflow:auto;vertical-align:top}
table{border-collapse:collapse;border-spacing:0}
*,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
html,body{font-size:100%}
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
a:hover{cursor:pointer}
img,object,embed{max-width:100%;height:auto}
object,embed{height:100%}
img{-ms-interpolation-mode:bicubic}
.left{float:left!important}
.right{float:right!important}
.text-left{text-align:left!important}
.text-right{text-align:right!important}
.text-center{text-align:center!important}
.text-justify{text-align:justify!important}
.hide{display:none}
img,object,svg{display:inline-block;vertical-align:middle}
textarea{height:auto;min-height:50px}
select{width:100%}
.center{margin-left:auto;margin-right:auto}
.spread{width:100%}
p.lead,.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{font-size:1.21875em;line-height:1.6}
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}
a{color:#2156a5;text-decoration:underline;line-height:inherit}
a:hover,a:focus{color:#1d4b8f}
a img{border:none}
p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
p aside{font-size:.875em;line-height:1.35;font-style:italic}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
h1{font-size:2.125em}
h2{font-size:1.6875em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
h4,h5{font-size:1.125em}
h6{font-size:1em}
hr{border:solid #ddddd8;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}
em,i{font-style:italic;line-height:inherit}
strong,b{font-weight:bold;line-height:inherit}
small{font-size:60%;line-height:inherit}
code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
ul,ol{margin-left:1.5em}
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}
ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
ul.square{list-style-type:square}
ul.circle{list-style-type:circle}
ul.disc{list-style-type:disc}
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
dl dt{margin-bottom:.3125em;font-weight:bold}
dl dd{margin-bottom:1.25em}
abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}
abbr{text-transform:none}
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}
blockquote cite:before{content:"\2014 \0020"}
blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
@media only screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
h1{font-size:2.75em}
h2{font-size:2.3125em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
h4{font-size:1.4375em}}
table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede}
table thead,table tfoot{background:#f7f8f7;font-weight:bold}
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7}
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
.clearfix:before,.clearfix:after,.float-group:before,.float-group:after{content:" ";display:table}
.clearfix:after,.float-group:after{clear:both}
*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed;word-wrap:break-word}
*:not(pre)>code.nobreak{word-wrap:normal}
*:not(pre)>code.nowrap{white-space:nowrap}
pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed}
em em{font-style:normal}
strong strong{font-weight:400}
.keyseq{color:rgba(51,51,51,.8)}
kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
.keyseq kbd:first-child{margin-left:0}
.keyseq kbd:last-child{margin-right:0}
.menuseq,.menuref{color:#000}
.menuseq b:not(.caret),.menuref{font-weight:inherit}
.menuseq{word-spacing:-.02em}
.menuseq b.caret{font-size:1.25em;line-height:.8}
.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
b.button:before,b.button:after{position:relative;top:-1px;font-weight:400}
b.button:before{content:"[";padding:0 3px 0 2px}
b.button:after{content:"]";padding:0 2px 0 3px}
p a>code:hover{color:rgba(0,0,0,.9)}
#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
#header:before,#header:after,#content:before,#content:after,#footnotes:before,#footnotes:after,#footer:before,#footer:after{content:" ";display:table}
#header:after,#content:after,#footnotes:after,#footer:after{clear:both}
#content{margin-top:1.25em}
#content:before{content:none}
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #ddddd8}
#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px}
#header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap}
#header .details span:first-child{margin-left:-.125em}
#header .details span.email a{color:rgba(0,0,0,.85)}
#header .details br{display:none}
#header .details br+span:before{content:"\00a0\2013\00a0"}
#header .details br+span.author:before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
#header .details br+span#revremark:before{content:"\00a0|\00a0"}
#header #revnumber{text-transform:capitalize}
#header #revnumber:after{content:"\00a0"}
#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
#toc{border-bottom:1px solid #efefed;padding-bottom:.5em}
#toc>ul{margin-left:.125em}
#toc ul.sectlevel0>li>a{font-style:italic}
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
#toc li{line-height:1.3334;margin-top:.3334em}
#toc a{text-decoration:none}
#toc a:active{text-decoration:underline}
#toctitle{color:#7a2518;font-size:1.2em}
@media only screen and (min-width:768px){#toctitle{font-size:1.375em}
body.toc2{padding-left:15em;padding-right:0}
#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #efefed;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
#toc.toc2 ul ul{margin-left:0;padding-left:1em}
#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
body.toc2.toc-right{padding-left:0;padding-right:15em}
body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #efefed;left:auto;right:0}}
@media only screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
#toc.toc2{width:20em}
#toc.toc2 #toctitle{font-size:1.375em}
#toc.toc2>ul{font-size:.95em}
#toc.toc2 ul ul{padding-left:1.25em}
body.toc2.toc-right{padding-left:0;padding-right:20em}}
#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
#content #toc>:first-child{margin-top:0}
#content #toc>:last-child{margin-bottom:0}
#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em}
#footer-text{color:rgba(255,255,255,.8);line-height:1.44}
.sect1{padding-bottom:.625em}
@media only screen and (min-width:768px){.sect1{padding-bottom:1.25em}}
.sect1+.sect1{border-top:1px solid #efefed}
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
#content h1>a.anchor:before,h2>a.anchor:before,h3>a.anchor:before,#toctitle>a.anchor:before,.sidebarblock>.content>.title>a.anchor:before,h4>a.anchor:before,h5>a.anchor:before,h6>a.anchor:before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
table.tableblock>caption.title{white-space:nowrap;overflow:visible;max-width:0}
.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{color:rgba(0,0,0,.85)}
table.tableblock #preamble>.sectionbody>.paragraph:first-of-type p{font-size:inherit}
.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
.admonitionblock>table td.icon{text-align:center;width:80px}
.admonitionblock>table td.icon img{max-width:initial}
.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #ddddd8;color:rgba(0,0,0,.6)}
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px}
.exampleblock>.content>:first-child{margin-top:0}
.exampleblock>.content>:last-child{margin-bottom:0}
.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
.sidebarblock>:first-child{margin-top:0}
.sidebarblock>:last-child{margin-bottom:0}
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8}
.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1}
.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;padding:1em;font-size:.8125em}
.literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto;white-space:pre;word-wrap:normal}
@media only screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}}
@media only screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}}
.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)}
.listingblock pre.highlightjs{padding:0}
.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px}
.listingblock pre.prettyprint{border-width:0}
.listingblock>.content{position:relative}
.listingblock code[data-lang]:before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999}
.listingblock:hover code[data-lang]:before{display:block}
.listingblock.terminal pre .command:before{content:attr(data-prompt);padding-right:.5em;color:#999}
.listingblock.terminal pre .command:not([data-prompt]):before{content:"$"}
table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none}
table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45}
table.pyhltable td.code{padding-left:.75em;padding-right:0}
pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8}
pre.pygments .lineno{display:inline-block;margin-right:.25em}
table.pyhltable .linenodiv{background:none!important;padding-right:0!important}
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em}
.quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
.quoteblock blockquote{margin:0;padding:0;border:0}
.quoteblock blockquote:before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
.quoteblock .attribution{margin-top:.5em;margin-right:.5ex;text-align:right}
.quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)}
.quoteblock .quoteblock blockquote{padding:0 0 0 .75em}
.quoteblock .quoteblock blockquote:before{display:none}
.verseblock{margin:0 1em 1.25em 1em}
.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
.verseblock pre strong{font-weight:400}
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
.quoteblock .attribution br,.verseblock .attribution br{display:none}
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
.quoteblock.abstract{margin:0 0 1.25em 0;display:block}
.quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{text-align:left;word-spacing:0}
.quoteblock.abstract blockquote:before,.quoteblock.abstract blockquote p:first-of-type:before{display:none}
table.tableblock{max-width:100%;border-collapse:separate}
table.tableblock td>.paragraph:last-child p>p:last-child,table.tableblock th>p:last-child,table.tableblock td>p:last-child{margin-bottom:0}
table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
table.grid-all>thead>tr>.tableblock,table.grid-all>tbody>tr>.tableblock{border-width:0 1px 1px 0}
table.grid-all>tfoot>tr>.tableblock{border-width:1px 1px 0 0}
table.grid-cols>*>tr>.tableblock{border-width:0 1px 0 0}
table.grid-rows>thead>tr>.tableblock,table.grid-rows>tbody>tr>.tableblock{border-width:0 0 1px 0}
table.grid-rows>tfoot>tr>.tableblock{border-width:1px 0 0 0}
table.grid-all>*>tr>.tableblock:last-child,table.grid-cols>*>tr>.tableblock:last-child{border-right-width:0}
table.grid-all>tbody>tr:last-child>.tableblock,table.grid-all>thead:last-child>tr>.tableblock,table.grid-rows>tbody>tr:last-child>.tableblock,table.grid-rows>thead:last-child>tr>.tableblock{border-bottom-width:0}
table.frame-all{border-width:1px}
table.frame-sides{border-width:0 1px}
table.frame-topbot{border-width:1px 0}
th.halign-left,td.halign-left{text-align:left}
th.halign-right,td.halign-right{text-align:right}
th.halign-center,td.halign-center{text-align:center}
th.valign-top,td.valign-top{vertical-align:top}
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
th.valign-middle,td.valign-middle{vertical-align:middle}
table thead th,table tfoot th{font-weight:bold}
tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7}
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
p.tableblock>code:only-child{background:none;padding:0}
p.tableblock{font-size:1em}
td>div.verse{white-space:pre}
ol{margin-left:1.75em}
ul li ol{margin-left:1.5em}
dl dd{margin-left:1.125em}
dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
ul.unstyled,ol.unstyled{margin-left:0}
ul.checklist{margin-left:.625em}
ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em}
ul.inline{margin:0 auto .625em auto;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden}
ul.inline>li{list-style:none;float:left;margin-left:1.375em;display:block}
ul.inline>li>*{display:block}
.unstyled dl dt{font-weight:400;font-style:normal}
ol.arabic{list-style-type:decimal}
ol.decimal{list-style-type:decimal-leading-zero}
ol.loweralpha{list-style-type:lower-alpha}
ol.upperalpha{list-style-type:upper-alpha}
ol.lowerroman{list-style-type:lower-roman}
ol.upperroman{list-style-type:upper-roman}
ol.lowergreek{list-style-type:lower-greek}
.hdlist>table,.colist>table{border:0;background:none}
.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
td.hdlist1{font-weight:bold;padding-bottom:1.25em}
.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
.colist>table tr>td:first-of-type{padding:.4em .75em 0 .75em;line-height:1;vertical-align:top}
.colist>table tr>td:first-of-type img{max-width:initial}
.colist>table tr>td:last-of-type{padding:.25em 0}
.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd}
.imageblock.left,.imageblock[style*="float: left"]{margin:.25em .625em 1.25em 0}
.imageblock.right,.imageblock[style*="float: right"]{margin:.25em 0 1.25em .625em}
.imageblock>.title{margin-bottom:0}
.imageblock.thumb,.imageblock.th{border-width:6px}
.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
.image.left{margin-right:.625em}
.image.right{margin-left:.625em}
a.image{text-decoration:none;display:inline-block}
a.image object{pointer-events:none}
sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
sup.footnote a,sup.footnoteref a{text-decoration:none}
sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em 0;border-width:1px 0 0 0}
#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;text-indent:-1.05em;margin-bottom:.2em}
#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none}
#footnotes .footnote:last-of-type{margin-bottom:0}
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0}
.gist .file-data>table td.line-data{width:99%}
div.unbreakable{page-break-inside:avoid}
.big{font-size:larger}
.small{font-size:smaller}
.underline{text-decoration:underline}
.overline{text-decoration:overline}
.line-through{text-decoration:line-through}
.aqua{color:#00bfbf}
.aqua-background{background-color:#00fafa}
.black{color:#000}
.black-background{background-color:#000}
.blue{color:#0000bf}
.blue-background{background-color:#0000fa}
.fuchsia{color:#bf00bf}
.fuchsia-background{background-color:#fa00fa}
.gray{color:#606060}
.gray-background{background-color:#7d7d7d}
.green{color:#006000}
.green-background{background-color:#007d00}
.lime{color:#00bf00}
.lime-background{background-color:#00fa00}
.maroon{color:#600000}
.maroon-background{background-color:#7d0000}
.navy{color:#000060}
.navy-background{background-color:#00007d}
.olive{color:#606000}
.olive-background{background-color:#7d7d00}
.purple{color:#600060}
.purple-background{background-color:#7d007d}
.red{color:#bf0000}
.red-background{background-color:#fa0000}
.silver{color:#909090}
.silver-background{background-color:#bcbcbc}
.teal{color:#006060}
.teal-background{background-color:#007d7d}
.white{color:#bfbfbf}
.white-background{background-color:#fafafa}
.yellow{color:#bfbf00}
.yellow-background{background-color:#fafa00}
span.icon>.fa{cursor:default}
a span.icon>.fa{cursor:inherit}
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
.admonitionblock td.icon .icon-note:before{content:"\f05a";color:#19407c}
.admonitionblock td.icon .icon-tip:before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
.admonitionblock td.icon .icon-warning:before{content:"\f071";color:#bf6900}
.admonitionblock td.icon .icon-caution:before{content:"\f06d";color:#bf3400}
.admonitionblock td.icon .icon-important:before{content:"\f06a";color:#bf0000}
.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
.conum[data-value] *{color:#fff!important}
.conum[data-value]+b{display:none}
.conum[data-value]:after{content:attr(data-value)}
pre .conum[data-value]{position:relative;top:-.125em}
b.conum *{color:inherit!important}
.conum:not([data-value]):empty{display:none}
dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
h1,h2,p,td.content,span.alt{letter-spacing:-.01em}
p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
p,blockquote,dt,td.content,span.alt{font-size:1.0625rem}
p{margin-bottom:1.25rem}
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc}
.print-only{display:none!important}
@media print{@page{margin:1.25cm .75cm}
*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}
a{color:inherit!important;text-decoration:underline!important}
a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
a[href^="http:"]:not(.bare):after,a[href^="https:"]:not(.bare):after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
abbr[title]:after{content:" (" attr(title) ")"}
pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
thead{display:table-header-group}
svg{max-width:100%}
p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
#toc,.sidebarblock,.exampleblock>.content{background:none!important}
#toc{border-bottom:1px solid #ddddd8!important;padding-bottom:0!important}
.sect1{padding-bottom:0!important}
.sect1+.sect1{border:0!important}
#header>h1:first-child{margin-top:1.25rem}
body.book #header{text-align:center}
body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em 0}
body.book #header .details{border:0!important;display:block;padding:0!important}
body.book #header .details span:first-child{margin-left:0!important}
body.book #header .details br{display:block}
body.book #header .details br+span:before{content:none!important}
body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
.listingblock code[data-lang]:before{display:block}
#footer{background:none!important;padding:0 .9375em}
#footer-text{color:rgba(0,0,0,.6)!important;font-size:.9em}
.hide-on-print{display:none!important}
.print-only{display:block!important}
.hide-for-print{display:none!important}
.show-for-print{display:inherit!important}}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css">
</head>
<body class="book toc2 toc-left">
<div id="header">
<h1>Dew:一站式微服务解决方案 [2.x Building&#8230;&#8203;]</h1>
<div class="details">
<span id="author" class="author">v2.0.0-beta2</span><br>
</div>
<div id="toc" class="toc2">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#dew微服务体系_dew_microservice_system">1. Dew微服务体系 Dew Microservice System</a>
<ul class="sectlevel2">
<li><a href="#设计理念">1.1. 设计理念</a>
<ul class="sectlevel3">
<li><a href="#微服务架构的尴尬">1.1.1. 微服务架构的尴尬</a></li>
<li><a href="#dew设计理念">1.1.2. Dew设计理念</a></li>
</ul>
</li>
<li><a href="#项目结构">1.2. 项目结构</a></li>
</ul>
</li>
<li><a href="#架构设计_architecture_chapter">2. 架构设计 Architecture Chapter</a></li>
<li><a href="#编码开发_framework_chapter">3. 编码开发 Framework Chapter</a>
<ul class="sectlevel2">
<li><a href="#framework-quick-start">3.1. 框架快速入门</a>
<ul class="sectlevel3">
<li><a href="#需求分析">3.1.1. 需求分析</a></li>
<li><a href="#功能体验">3.1.2. 功能体验</a></li>
<li><a href="#模块设计">3.1.3. 模块设计</a></li>
<li><a href="#framework-quick-start-core-code-instructions">3.1.4. 核心代码说明</a></li>
</ul>
</li>
<li><a href="#framework-user-manual">3.2. 框架使用手册</a>
<ul class="sectlevel3">
<li><a href="#framework-user-manual-import">3.2.1. 引入方式</a></li>
<li><a href="#dew类介绍">3.2.2. Dew类介绍</a></li>
<li><a href="#常用工具集">3.2.3. 常用工具集</a></li>
<li><a href="#framework-user-manual-cluster">3.2.4. 集群功能</a></li>
<li><a href="#统一响应">3.2.5. 统一响应</a></li>
<li><a href="#异常处理">3.2.6. 异常处理</a></li>
<li><a href="#数据验证">3.2.7. 数据验证</a></li>
<li><a href="#cors支持">3.2.8. CORS支持</a></li>
<li><a href="#framework-user-manual-auth">3.2.9. 权限认证</a></li>
<li><a href="#测试支持">3.2.10. 测试支持</a></li>
<li><a href="#幂等处理">3.2.11. 幂等处理</a></li>
<li><a href="#api文档">3.2.12. API文档</a></li>
<li><a href="#代码质量检查">3.2.13. 代码质量检查</a></li>
</ul>
</li>
<li><a href="#framework-configuration">3.3. 框架配置速查</a>
<ul class="sectlevel3">
<li><a href="#code_dew_code_参数">3.3.1. <code>Dew</code> 参数</a></li>
<li><a href="#code_spring_boot_code_核心参数">3.3.2. <code>Spring boot</code> 核心参数</a></li>
<li><a href="#code_spring_cloud_code_核心参数">3.3.3. <code>Spring cloud</code> 核心参数</a></li>
</ul>
</li>
<li><a href="#framework-best-practices">3.4. 框架最佳实践</a>
<ul class="sectlevel3">
<li><a href="#项目模块设计">3.4.1. 项目模块设计</a></li>
<li><a href="#代码包设计">3.4.2. 代码包设计</a></li>
<li><a href="#开发期服务调用">3.4.3. 开发期服务调用</a></li>
<li><a href="#代码质量管理">3.4.4. 代码质量管理</a></li>
<li><a href="#code_validated_code_注解">3.4.5. <code>@Validated</code> 注解</a></li>
<li><a href="#code_jackson_code_对于_code_java8_code_时间转换_code_springmvc_code_以_code_jackson_code_接收_code_json_code_数据">3.4.6. <code>jackson</code> 对于 <code>Java8</code> 时间转换（ <code>SpringMVC</code> 以 <code>jackson</code> 接收 <code>json</code> 数据）</a></li>
<li><a href="#缓存处理">3.4.7. 缓存处理</a></li>
<li><a href="#jdbc_批量插入性能问题">3.4.8. jdbc 批量插入性能问题</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#部署运维_devops_chapter">4. 部署运维 DevOps Chapter</a>
<ul class="sectlevel2">
<li><a href="#devops-quick-start">4.1. DevOps快速入门</a>
<ul class="sectlevel3">
<li><a href="#功能体验_2">4.1.1. 功能体验</a></li>
<li><a href="#发生了什么">4.1.2. 发生了什么？</a></li>
</ul>
</li>
<li><a href="#devops-user-manual">4.2. DevOps使用手册</a>
<ul class="sectlevel3">
<li><a href="#devops-user-manual-import">4.2.1. 引入方式</a></li>
<li><a href="#支持的项目类型">4.2.2. 支持的项目类型</a></li>
<li><a href="#nodejs项目的特殊处理">4.2.3. NodeJS项目的特殊处理</a></li>
<li><a href="#项目配置">4.2.4. 项目配置</a></li>
<li><a href="#打包部署">4.2.5. 打包部署</a></li>
<li><a href="#版本回滚">4.2.6. 版本回滚</a></li>
<li><a href="#应用卸载">4.2.7. 应用卸载</a></li>
<li><a href="#日志查看">4.2.8. 日志查看</a></li>
<li><a href="#伸缩扩展">4.2.9. 伸缩扩展</a></li>
<li><a href="#应用刷新">4.2.10. 应用刷新</a></li>
<li><a href="#ci_cd工具支持_gitlab_ci">4.2.11. CI/CD工具支持-Gitlab CI</a></li>
<li><a href="#执行通知">4.2.12. 执行通知</a></li>
<li><a href="#dew-devops-deploy">4.2.13. Dew DevOps 脚本部署</a></li>
<li><a href="#监控管理">4.2.14. 监控管理</a></li>
</ul>
</li>
<li><a href="#devops-env-install">4.3. 测试、生产环境安装配置</a>
<ul class="sectlevel3">
<li><a href="#服务规划">4.3.1. 服务规划</a></li>
<li><a href="#基础配置">4.3.2. 基础配置</a></li>
<li><a href="#docker">4.3.3. Docker</a></li>
<li><a href="#kubernetes">4.3.4. Kubernetes</a></li>
<li><a href="#helm">4.3.5. Helm</a></li>
<li><a href="#metrics_server">4.3.6. Metrics Server</a></li>
<li><a href="#nginx_ingress_controller">4.3.7. Nginx Ingress Controller</a></li>
<li><a href="#dashboard">4.3.8. Dashboard</a></li>
<li><a href="#elasticsearch">4.3.9. Elasticsearch</a></li>
<li><a href="#kibana">4.3.10. Kibana</a></li>
<li><a href="#prometheus_grafana">4.3.11. Prometheus &amp; Grafana</a></li>
<li><a href="#fluentd">4.3.12. Fluentd</a></li>
<li><a href="#jaeger">4.3.13. Jaeger</a></li>
</ul>
</li>
<li><a href="#devops-configuration">4.4. DevOps配置速查</a>
<ul class="sectlevel3">
<li><a href="#devops-configuration-dew">4.4.1. .dew 配置</a></li>
<li><a href="#maven_配置">4.4.2. Maven 配置</a></li>
</ul>
</li>
<li><a href="#devops-best-practices">4.5. DevOps最佳实践</a>
<ul class="sectlevel3">
<li><a href="#为什么不用_fabric8">4.5.1. 为什么不用 fabric8</a></li>
<li><a href="#devops-best-practices-auth">4.5.2. 鉴权处理</a></li>
<li><a href="#统一配置处理">4.5.3. 统一配置处理</a></li>
<li><a href="#jvm设置">4.5.4. JVM设置</a></li>
<li><a href="#配置中心选择">4.5.5. 配置中心选择</a></li>
<li><a href="#spring_cloud_项目的_dew_gitlab_ci_cd_实践">4.5.6. Spring Cloud 项目的 Dew &amp; Gitlab CI/CD 实践</a></li>
</ul>
</li>
<li><a href="#devops示例">4.6. DevOps示例</a>
<ul class="sectlevel3">
<li><a href="#devops示例_helloworld后端服务">4.6.1. DevOps示例 : Helloworld后端服务</a></li>
<li><a href="#devops示例_helloworld类库">4.6.2. DevOps示例 : Helloworld类库</a></li>
<li><a href="#devops示例_helloworld前端">4.6.3. DevOps示例 : Helloworld前端</a></li>
<li><a href="#devops示例_to_do">4.6.4. DevOps示例 : To-Do</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#dew项目信息_dew_project_info">5. Dew项目信息 Dew Project Info</a>
<ul class="sectlevel2">
<li><a href="#开发指南">5.1. 开发指南</a>
<ul class="sectlevel3">
<li><a href="#代码要求">5.1.1. 代码要求</a></li>
<li><a href="#环境要求">5.1.2. 环境要求</a></li>
</ul>
</li>
<li><a href="#版本">5.2. 版本</a>
<ul class="sectlevel3">
<li><a href="#通道说明">5.2.1. 通道说明</a></li>
<li><a href="#发行记录">5.2.2. 发行记录</a></li>
</ul>
</li>
<li><a href="#路线图">5.3. 路线图</a></li>
</ul>
</li>
<li><a href="#附录_appendix">6. 附录 Appendix</a>
<ul class="sectlevel2">
<li><a href="#proxies">6.1. 代理选择与设置</a>
<ul class="sectlevel3">
<li><a href="#安装配置">6.1.1. 安装配置</a></li>
</ul>
</li>
<li><a href="#middleware">6.2. 常用中间件安装配置</a>
<ul class="sectlevel3">
<li><a href="#postgresql">6.2.1. PostgreSql</a></li>
<li><a href="#redis">6.2.2. Redis</a></li>
<li><a href="#mysql">6.2.3. MySQL</a></li>
<li><a href="#mongo">6.2.4. Mongo</a></li>
<li><a href="#gitlab">6.2.5. Gitlab</a></li>
<li><a href="#harbor">6.2.6. Harbor</a></li>
<li><a href="#dnsmasq">6.2.7. Dnsmasq</a></li>
<li><a href="#ntpdate">6.2.8. NTPDate</a></li>
<li><a href="#minio">6.2.9. Minio</a></li>
<li><a href="#nfs">6.2.10. NFS</a></li>
<li><a href="#glusterfs">6.2.11. GlusterFS</a></li>
</ul>
</li>
<li><a href="#dew-2-migration-guide">6.3. Dew 2.x Migration guide[TBD]</a>
<ul class="sectlevel3">
<li><a href="#初始准备">6.3.1. 初始准备</a></li>
<li><a href="#迁移步骤">6.3.2. 迁移步骤</a></li>
</ul>
</li>
<li><a href="#内置image与chart">6.4. 内置Image与Chart</a>
<ul class="sectlevel3">
<li><a href="#dew_docker_image_集成kubectl与helm">6.4.1. Dew Docker Image : 集成Kubectl与Helm</a></li>
<li><a href="#dew_docker_image_集成ci_cd能力">6.4.2. Dew Docker Image : 集成CI/CD能力</a></li>
<li><a href="#dew_docker_image_spring_cloud_config">6.4.3. Dew Docker Image : Spring Cloud Config</a></li>
<li><a href="#helm-chart-spring-cloud-config">6.4.4. Dew Helm Chart : Spring Cloud Config</a></li>
</ul>
</li>
<li><a href="#支持的ci_cd服务">6.5. 支持的CI/CD服务</a>
<ul class="sectlevel3">
<li><a href="#dew-devops-maven-agent">6.5.1. Dew DevOps Maven Agent</a></li>
<li><a href="#dew-gitlab-ci">6.5.2. Dew CI/CD : Gitlab CI 实现</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</div>
<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>Master分支正在构建如下功能：</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>升级到 Spring Boot 2.x</p>
</li>
<li>
<p>全面容器化，以K8s为容器平台，兼容Spring Cloud与Service Mesh两类微服务体系</p>
</li>
<li>
<p>支撑中间件优先使用CNCF下的产品，如使用 jaeger 代替 zipkin</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>如需要稳定、纯粹的Spring Cloud扩展请切换到 <a href="https://github.com/gudaoxuri/dew/tree/1.5.1-RC">1.5.1-RC</a> tag.</p>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="dew微服务体系_dew_microservice_system"><a class="anchor" href="#dew微服务体系_dew_microservice_system"></a>1. Dew微服务体系 Dew Microservice System</h2>
<div class="sectionbody">
<div class="imageblock">
<div class="content">
<a class="image" href="https://travis-ci.org/gudaoxuri/dew"><img src="https://img.shields.io/travis/gudaoxuri/dew.svg" alt="dew"></a>
</div>
</div>
<div class="imageblock">
<div class="content">
<a class="image" href="https://app.codacy.com/app/gudaoxuri/dew?utm_source=github.com&utm_medium=referral&utm_content=gudaoxuri/dew&utm_campaign=Badge_Grade_Dashboard"><img src="https://api.codacy.com/project/badge/Grade/aacfdad1579043f0a2c1928b53096b7b" alt="aacfdad1579043f0a2c1928b53096b7b"></a>
</div>
</div>
<div class="imageblock">
<div class="content">
<a class="image" href="https://www.apache.org/licenses/LICENSE-2.0.txt"><img src="https://img.shields.io/badge/license-ASF2-blue.svg" alt="Apache License 2"></a>
</div>
</div>
<div class="paragraph">
<p>微服务一站式解决方案( <a href="http://doc.dew.ms" class="bare">http://doc.dew.ms</a> )，提供：架构指南、容器优先/兼容Spring Cloud与Istio的框架、最佳实践及DevOps标准化流程。</p>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>Dew [du:] 意为 <code>露水</code> ，希望此体系可以像晨间的露水一样透明、静谧、丰盈。让使用者尽量不要感知Dew的存在，专注业务实现。</p>
</div>
</blockquote>
</div>
<div class="sect2">
<h3 id="设计理念"><a class="anchor" href="#设计理念"></a>1.1. 设计理念</h3>
<div class="sect3">
<h4 id="微服务架构的尴尬"><a class="anchor" href="#微服务架构的尴尬"></a>1.1.1. 微服务架构的尴尬</h4>
<div class="paragraph">
<p>几乎人人都在谈微服务，每个IT企业都在做微服务架构，但大部分项目都会存在这样的尴尬：</p>
</div>
<div class="ulist">
<ul>
<li>
<p>什么是微服务？怎么做微服务架构？为什么这么乱？</p>
</li>
</ul>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p><strong>缺乏微服务架构设计思想</strong> 导致成功的微服务项目屈指可数，只听说微服务的好，却不知微服务的坑</p>
</div>
</blockquote>
</div>
<div class="ulist">
<ul>
<li>
<p>架构好了，框架怎么选择？ dubbo、Spring Boot/Cloud、Istio、Vert.x、还是自研？大一点的企业都会选择自研，但自研又会遇到如下问题：</p>
<div class="ulist">
<ul>
<li>
<p>无法传承，框架的研发人员离职后没有可以接手</p>
</li>
<li>
<p>上手难度大，很多框架喜欢重复造轮子，做出来的与业界主流思想/标准格格不入，导致学习培训成本很高</p>
</li>
<li>
<p>功能片面，不通用，服务框架讲求通用性，尽量让整个公司使用同一套规范以方便维护，但很多框架只实现了某些特定场景的功能，无法通用化</p>
</li>
<li>
<p>维护成本高，尤其是对于完全自研的框架，往往需要专职人员维护</p>
</li>
<li>
<p>与主流脱节，无法分享微服务化、容器化、服务网格化的红利</p>
</li>
</ul>
</div>
</li>
</ul>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p><strong>没有合适的微服务框架</strong> 导致人员技能要求高、项目研发成本高</p>
</div>
</blockquote>
</div>
<div class="ulist">
<ul>
<li>
<p>框架选型也有了，但怎么测试、发布与运维？都在说容器化，要怎么做？</p>
</li>
</ul>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p><strong>缺少一体化的研发流程支撑</strong> 导致各项目规范不统一、发布效率低、容器化问题频出</p>
</div>
</blockquote>
</div>
</div>
<div class="sect3">
<h4 id="dew设计理念"><a class="anchor" href="#dew设计理念"></a>1.1.2. Dew设计理念</h4>
<div class="paragraph">
<p>上述问题是Dew必须面对的，应对的设计核心理念是：</p>
</div>
<div class="listingblock">
<div class="content">
<pre>提供微服务架构指南 + 扩展主流微服务框架 + 标准化DevOps流程</pre>
</div>
</div>
<div class="paragraph">
<div class="title"><strong>提供微服务架构指南</strong></div>
<p>项目要上微服务，其架构思想是前提，《微服务架构设计》(<a href="https://gudaoxuri.gitbook.io/microservices-architecture" class="bare">https://gudaoxuri.gitbook.io/microservices-architecture</a>) 做为入门书籍非常合适。</p>
</div>
<div class="olist arabic">
<div class="title"><strong>扩展主流微服务框架</strong></div>
<ol class="arabic">
<li>
<p>简单，用最通用的、标准的、开发人员都熟悉的开发模型</p>
</li>
<li>
<p>全面，尽量重用市场已有能力实现，减少框架自身的维护成本</p>
</li>
<li>
<p>轻量，原则上不引入高侵入性的三方框架/类库</p>
</li>
<li>
<p>可替换，只做扩展，尽量不修改基础框架代码，开发人员完全可以直接基于基础框架开发</p>
</li>
<li>
<p>主流，整合流行的微服务框架</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>实现上我们选择 <code>Spring Boot</code> 这一业界主流框架，对上兼容 <code>Spring Cloud</code> 与 <code>Istio</code>。</p>
</div>
<div class="paragraph">
<div class="title"><strong>标准化DevOps流程</strong></div>
<p>目前市场有两种主流的DevOps做法：</p>
</div>
<div class="ulist">
<ul>
<li>
<p>以 <code>Jenkins X</code> 、 <code>Gitlab CI</code> 为代表的CI/CD工具主导的DevOps流程</p>
</li>
</ul>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p><strong>集成度低、操作不友好</strong></p>
</div>
<div class="paragraph">
<p>DevOps是一个系统性工程，想通过传统的CI/CD工具的方法实现难度较大</p>
</div>
</blockquote>
</div>
<div class="ulist">
<ul>
<li>
<p>以 <a href="http://https://develop.aliyun.com/devops">阿里云</a> 、 <a href="https://azure.microsoft.com/zh-cn/solutions/devops/">Azure</a>
为代表的云厂商DevOps方案</p>
</li>
</ul>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p><strong>平台依赖，不通用</strong></p>
</div>
<div class="paragraph">
<p>云厂商方案做了高度集成，弥补了工具型方案的不足，但问题也很明显，各厂商都有自己的一套标准，互不兼容，导致平台绑定，无法迁移</p>
</div>
</blockquote>
</div>
<div class="paragraph">
<p><code>Dew</code> 在DevOps上另辟蹊径，以Maven为核心构建，其优点在于：</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>Maven是行业标准，并且兼容除JVM外的其它语言环境，可支撑所有主流项目</p>
</li>
<li>
<p>可在本地执行完成DevOps开发流程，也可与CI/CD工具整合提供测试/生产等环境的DevOps</p>
</li>
<li>
<p>对CI/CD工具的依赖度低、开源、标准化，无厂商绑定</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>基于这一理念，我们提供的一体化的Maven插件，实现了：</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>CI/CD流程支持，自动化依赖管理、测试、质检、打包与发布、回滚、远程Debug等</p>
</li>
<li>
<p>全面容器化，所有环境均为容器方案</p>
</li>
<li>
<p>支持与 <code>Gitlab CI</code> 或其它 CI/CD服务整合</p>
</li>
<li>
<p>实现针对JVM服务、类库、前端等主流项目的支撑</p>
</li>
</ol>
</div>
<div class="exampleblock">
<div class="content">
<div class="paragraph">
<p>一言蔽之， <code>Dew</code> 致力于成为微服务一站式解决方案。</p>
</div>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="项目结构"><a class="anchor" href="#项目结构"></a>1.2. 项目结构</h3>
<div class="listingblock">
<div class="content">
<pre>|- framework
|-  |- modules
|-  |-  |- parent-starter                  // 父Pom模块
|-  |-  |- boot-starter                    // 核心模块，包含Spring Boot Web相关依赖
|-  |-  |- cloud-starter                   // Cloud模块，包含Spring Cloud Kuberenetes相关依赖
|-  |-  |- cluster-common                  // 集群能力接口
|-  |-  |- cluster-common-test             // 集群测试模块
|-  |-  |- cluster-hazelcast               // Hazelcast集群能力实现
|-  |-  |- cluster-rabbit                  // RabbitMQ集群能力实现
|-  |-  |- cluster-redis                   // Redis集群能力实现
|-  |-  |- cluster-mqtt                    // MQTT集群能力实现
|-  |-  |- idempotent-starter              // 幂等处理模块
|-  |-  |- notification                    // 通知处理模块
|-  |-  |- test-starter                    // 单元测试模块
|-  |-  |- hbase-starter                   // Spring Boot HBase Starter 模块
|-  |- components                          // 集成的服务
|-  |-  |- auth                            // 认证服务
|-  |-  |- auth-sdk-starter                // 认证服务SDK
|-  |-  |- config                          // Spring Cloud服务
|-  |- checkstyle                          // 项目CheckStyle
|- devops                                  // DevOps部分
|-  |- maven                               // DevOps使用到的Maven插件
|-  |-  |- dew-maven-plugin                // DevOps核心插件
|-  |- sh                                  // DevOps执行脚本
|-  |- chart                               // DevOps使用到的Helm Chart
|-  |-  |- dew-spring-cloud-config         //  Spring Cloud Config Chart
|-  |- cicd                                // 各CI服务的 CI/CD 配置
|-  |-  |- gitlabci                        // Gitlab CI CI/CD配置
|-  |- docker                              // DevOps使用到的镜像
|-  |-  |- dew-devops                      // 集成 Java Maven Node Git 的镜像
|-  |-  |- dew-k8s-native-client           // 集成 Kuberenetes 及 Docker 的镜像
|-  |-  |- dew-spring-cloud-config         // Spring Cloud Config 镜像
|-  |- it                                  // 集成测试
|- docs                                    // 文档</pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="架构设计_architecture_chapter"><a class="anchor" href="#架构设计_architecture_chapter"></a>2. 架构设计 Architecture Chapter</h2>
<div class="sectionbody">
<div class="paragraph">
<p>参见本书：</p>
</div>
<div class="paragraph">
<p><a href="https://gudaoxuri.gitbook.io/microservices-architecture/" class="bare">https://gudaoxuri.gitbook.io/microservices-architecture/</a></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="编码开发_framework_chapter"><a class="anchor" href="#编码开发_framework_chapter"></a>3. 编码开发 Framework Chapter</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="framework-quick-start"><a class="anchor" href="#framework-quick-start"></a>3.1. 框架快速入门</h3>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
本文以 To-Do 项目为示例讲解 <code>Dew</code> 框架部分的入门操作，项目地址： <a href="https://github.com/dew-ms/devops-example-todo" class="bare">https://github.com/dew-ms/devops-example-todo</a> 。
</td>
</tr>
</table>
</div>
<div class="sect3">
<h4 id="需求分析"><a class="anchor" href="#需求分析"></a>3.1.1. 需求分析</h4>
<div class="paragraph">
<p>To-Do项目实现一个简单的任务记录功能，要求支持：</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>对任务的添加、删除，任务列表的查看等基础功能</p>
</li>
<li>
<p>加入类似Excel的公式计算能力，所有以 <code>=</code> 号开头的任务均被视为计算公式，输入后返回计算后的值，
例如输入 <code>=1024*1024</code> 返回<code>1048576</code></p>
</li>
<li>
<p>终端支持H5、微信小程序等平台</p>
</li>
</ol>
</div>
</div>
<div class="sect3">
<h4 id="功能体验"><a class="anchor" href="#功能体验"></a>3.1.2. 功能体验</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
体验环境要求安装 Java(&gt;=8)、Maven、NodeJS(&gt;=8)
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="content">
<pre>git clone https://github.com/dew-ms/devops-example-todo.git
cd devops-example-todo
# 先执行安装
mvn install -Dmaven.test.skip=true
# 打开两个命令窗口分别启动两个组件（各组件的作用后文会说明）
mvn spring-boot:run -pl backend/services/kernel
mvn spring-boot:run -pl backend/services/compute
# 打开新的命令窗口，启动前端
cd frontend &amp;&amp; npm install &amp;&amp; npm run dev:h5
# 自动打开浏览器，切换到移动模式体验</pre>
</div>
</div>
<div class="imageblock">
<div class="content">
<img src="./images/todo-demo.gif" alt="todo demo">
</div>
</div>
</div>
<div class="sect3">
<h4 id="模块设计"><a class="anchor" href="#模块设计"></a>3.1.3. 模块设计</h4>
<div class="paragraph">
<p>此程序比较简单，核心能力由 <code>kernel</code> 组件提供，
考虑到公式计算对CPU的要求较高，所以独立成 <code>compute</code> 组件，由 <code>kernel</code> 发起调用，同时这也演示了服务间Rest调用。
另外程序添加 <code>notifier</code> 组件，所有操作都可发起通知，用于演示MQ调用。
三个组件共用的代码放在 <code>common</code> 模块中。</p>
</div>
<div class="paragraph">
<p>为兼容不同终端，前端使用<code>taro</code> 框架。</p>
</div>
<div class="paragraph">
<p>目录结构如下：</p>
</div>
<div class="listingblock">
<div class="content">
<pre>|- backend                  // 后端服务
|-  |- libraries            // 类库
|-  |-  |- common           // 公共模块，三个服务组件都依赖于此
|-  |- services             // 服务组件
|-  |-  |- kernel           // 核心服务，与前端交互的唯一入口
|-  |-  |- compute          // 公式计算服务，由kernel通过Rest调用
|-  |-  |- notifier         // 通知服务，由kernel通过MQ调用
|- frontend                 // 前端
|- pom.xml                  // 父Pom</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="framework-quick-start-core-code-instructions"><a class="anchor" href="#framework-quick-start-core-code-instructions"></a>3.1.4. 核心代码说明</h4>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
这里只关注后端的实现，前端代码不展开说明。
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">pom.xml</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">&lt;!-- 引用 Dew的 parent-starter --&gt;
&lt;parent&gt;
    &lt;groupId&gt;ms.dew&lt;/groupId&gt;
    &lt;artifactId&gt;parent-starter&lt;/artifactId&gt;
    &lt;version&gt;...&lt;/version&gt;
&lt;/parent&gt;

&lt;!-- ... --&gt;</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">backend/libraries/common/pom.xml</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">&lt;!-- ... --&gt;

&lt;dependencies&gt;
    &lt;!-- 引用 cloud-starter 实现分布式能力  --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;ms.dew&lt;/groupId&gt;
        &lt;artifactId&gt;cloud-starter&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!-- 添加JPA支持  --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;

&lt;!-- ... --&gt;</code></pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
详见 <a href="#framework-user-manual-import">引入方式</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">backend/services/kernel/pom.xml</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">&lt;!-- ... --&gt;

    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;ms.dew.devops.it&lt;/groupId&gt;
            &lt;artifactId&gt;todo-common&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!-- 引用 cluster-spi-redis 实现Dew集群能力的Redis实现 --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;ms.dew&lt;/groupId&gt;
            &lt;artifactId&gt;cluster-spi-redis&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;ms.dew&lt;/groupId&gt;
            &lt;artifactId&gt;test-starter&lt;/artifactId&gt;
            &lt;!--仅用于演示， 启用内嵌的Redis及 H2--&gt;
            &lt;scope&gt;compile&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;

&lt;!-- ... --&gt;</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">backend/services/kernel/resources/bootstrap.yml</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">dew:
  cluster:
    mq: redis # 使用Redis做为Dew集群的MQ实现

# ...</code></pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
详见 <a href="#framework-user-manual-cluster">集群功能</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">backend/services/kernel/resources/bootstrap-default.yml</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">spring:
  cloud:
    config:
      enabled: false  # 关闭统一配置
    kubernetes:
      ribbon:
        enabled: false # 关闭ribbon
  redis: # Redis配置
    host: localhost
    port: 6379
    database: 0
  datasource: # DB配置
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:test

todo-compute:
  ribbon: # 使用自定义ribbon列表
    listOfServers: localhost:8082

# ...</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">backend/libraries/common/ms.dew.devops.it.todo.common.TodoParentApplication.java</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">/**
 * 空实现，做为所有组件启动类的父类
 */
// 启用 Spring Cloud 能力
@SpringCloudApplication
public class TodoParentApplication {

}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">backend/services/kernel/ms.dew.devops.it.todo.kernel.TodoKernelApplication.java</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">// 继承自TodoParentApplication
public class TodoKernelApplication extends TodoParentApplication {

    // 启动类
    public static void main(String[] args) {
        new SpringApplicationBuilder(TodoKernelApplication.class).run(args);
    }

}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">backend/services/kernel/ms.dew.devops.it.todo.kernel.controller.TodoController.java</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">@RestController
// Swagger文档注解
@Api("TODO示例")
@RequestMapping("/api")
public class TodoController {

    @Autowired
    private TodoService todoService;

    /**
     * Add int.
     *
     * @param content the content
     * @return the int
     */
    @PostMapping("")
    @ApiOperation(value = "添加Todo记录")
    public Todo add(@RequestBody String content) {
        return todoService.add(content);
    }

    // ...

}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">backend/services/kernel/ms.dew.devops.it.todo.kernel.service.TodoService.java</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">@Service
public class TodoService {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * Add int.
     *
     * @param content the content
     * @return id int
     */
    public Todo add(String content) {
        if (content.trim().startsWith("=")) {
            // 去掉 = 号
            content = content.trim().substring(1);
            // 此为幂等修改操作，故使用 put 方法
            // restTemplate 的 put 方法没有返回值，只能使用此方式
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.TEXT_PLAIN);
            HttpEntity&lt;String&gt; entity = new HttpEntity&lt;&gt;(content, headers);
            // 使用Spring的 restTemplate 实现服务间 rest 调用
            content = restTemplate
                    .exchange("http://" + Constants.REST_COMPUTE_SERVICE + "/compute", HttpMethod.PUT, entity, String.class)
                    .getBody();
        }
        ...
        // 使用Dew的集群MQ功能实现消息点对点发送
        Dew.cluster.mq.request(Constants.MQ_NOTIFY_TODO_ADD, $.json.toJsonString(todo));
        return todo;
    }

    // ...
}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">backend/services/notifier/ms.dew.devops.it.todo.notifier.controller.NotifierController.java</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">@RestController
public class NotifierController {

    private static final Logger logger = LoggerFactory.getLogger(NotifierController.class);

    @PostConstruct
    public void processTodoAddEvent() {
        // 使用Dew的集群MQ功能实现消息点对点接收
        Dew.cluster.mq.response(Constants.MQ_NOTIFY_TODO_ADD, todo -&gt; {
            logger.info("Received add todo event :" + todo);
        });
    }

    // ...
}</code></pre>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="framework-user-manual"><a class="anchor" href="#framework-user-manual"></a>3.2. 框架使用手册</h3>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<code>Dew</code> 框架部分是对 <code>Spring Boot/Cloud</code> 的扩展，使用之前务必了解相关框架的基础知识。
</td>
</tr>
</table>
</div>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
本手册只介绍 <code>Dew</code> 框架部分的扩展功能！
</td>
</tr>
</table>
</div>
<div class="sect3">
<h4 id="framework-user-manual-import"><a class="anchor" href="#framework-user-manual-import"></a>3.2.1. 引入方式</h4>
<div class="listingblock">
<div class="content">
<pre>此章节关联示例:examples/bone-example</pre>
</div>
</div>
<div class="paragraph">
<p><code>Dew</code> 所有模块均为Maven结构，使用如下：</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">&lt;!--引入Dew父依赖，也可以使用import方式--&gt;
&lt;parent&gt;
    &lt;groupId&gt;ms.dew&lt;/groupId&gt;
    &lt;artifactId&gt;parent-starter&lt;/artifactId&gt;
    &lt;!--生产环境请选择合适的版本!--&gt;
    &lt;version&gt;${dew.version}&lt;/version&gt;
&lt;/parent&gt;
...
&lt;dependencies&gt;
    &lt;!--引入需要的模块--&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;ms.dew&lt;/groupId&gt;
        &lt;artifactId&gt;对应模块名，见下文&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;
...
&lt;!--开发者介绍--&gt;
&lt;developers&gt;
    &lt;developer&gt;
        &lt;name&gt;...&lt;/name&gt;
        &lt;email&gt;...&lt;/email&gt;
    &lt;/developer&gt;
&lt;/developers&gt;
&lt;!--SCM信息--&gt;
&lt;scm&gt;
    &lt;connection&gt;...&lt;/connection&gt;
    &lt;developerConnection&gt;...&lt;/developerConnection&gt;
    &lt;url&gt;&lt;/url&gt;
&lt;/scm&gt;
...</code></pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<code>parent-starter</code> 中已包含各模块的版本，引用模块依赖时可省略版本号。
</td>
</tr>
</table>
</div>
<table class="tableblock frame-all grid-all spread">
<caption class="title">Table 1. 功能模块</caption>
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">模块名</th>
<th class="tableblock halign-left valign-top">核心功能</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">parent-starter</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">父Pom模块</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">boot-starter</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">核心模块，包含Spring Boot Web相关依赖</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">cloud-starter</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Cloud模块，包含Spring Cloud Kuberenetes相关依赖</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">cluster-common</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">集群能力接口</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">cluster-hazelcast</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Hazelcast集群能力实现</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">cluster-rabbit</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">RabbitMQ集群能力实现</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">cluster-redis</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Redis集群能力实现</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">cluster-mqtt</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">MQTT集群能力实现</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">hbase-starter</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">HBase Spring Boot 实现</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">idempotent-starter</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">幂等处理模块</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">notification</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">通知处理模块</p></td>
</tr>
</tbody>
</table>
</div>
<div class="sect3">
<h4 id="dew类介绍"><a class="anchor" href="#dew类介绍"></a>3.2.2. Dew类介绍</h4>
<div class="paragraph">
<p><code>Dew</code>类包装了一些常用的功能，是Dew的能力的主要输出口。</p>
</div>
<div class="listingblock">
<div class="title"><code>Dew</code>功能说明</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">// 获取集群操作能力，见下文
Dew.cluster.xx
// 获取通知操作能力，见下文
Dew.notify.xx
// 获取Spring上下文
Dew.applicationContext.xx
// 获取Dew配置，说见 框架配置速查
Dew.dewConfig.xx
// 获取认证处理能力，见下文
Dew.auth.xx

//  ============ 获取请求上下文信息  ============
// 当次请求的ID
Dew.context().getId()
// 请求来源IP
Dew.context().getSourceIP()
// 请求最初的URL
Dew.context().getRequestUri()
// 请求对应的token，详见下文
Dew.context().getToken()
// 请求对应的操作者信息，详见下文
Dew.context().optInfo()

// ============ 获取当前组件基础信息 ============
// 应用名称，对应为 spring.application.name
Dew.Info.name
// 应用环境，对应为 spring.profiles.active
Dew.Info.profile
// 应用主机Web端口，对应为 server.port
Dew.Info.webPort
// 应用主机IP
Dew.Info.ip
// 应用主机Host
Dew.Info.host
// 应用实例，各组件实例唯一
Dew.Info.instance

// ============ 定时任务操作 ============
// 此类下的操作会自动带入Dew.context()
/**
* 设定一个周期性调度任务.
*
* @param initialDelaySec 延迟启动的秒数
* @param periodSec       周期调度秒数
* @param fun             调度方法
*/
Dew.Timer.periodic(long initialDelaySec, long periodSec, VoidExecutor fun)
/**
* 设定一个定时任务.
*
* @param delaySec 延迟启动的秒数
* @param fun      定时任务方法
*/
Dew.Timer.timer(long delaySec, VoidExecutor fun)

// ============ 常用工具 ============
/**
* 获取真实IP.
*
* @param request 请求信息
* @return 真实的IP
*/
Dew.Util.getRealIP(HttpServletRequest request)
**
* 获取真实IP.
*
* @param requestHeader     请求头信息
* @param defaultRemoteAddr 缺省的IP地址
* @return 真实的IP
*/
Dew.Util.getRealIP(Map&lt;String, String&gt; requestHeader, String defaultRemoteAddr)
/**
* 创建一个新的线程.
* &lt;p&gt;
* 自动带入Dew.context()
*
* @param fun 执行的方法
*/
Dew.Util.newThread(Runnable fun)

/**
* 统一异常处理.
* &lt;p&gt;
* 封装任意异常到统一的格式，见下文
*
* @param &lt;E&gt;            上抛的异常类型
* @param code           异常编码
* @param ex             上抛的异常对象
* @param customHttpCode 自定义Http状态码
* @return 上抛的异常对象
*/
Dew.E.e(String code, E ex, int customHttpCode)</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="常用工具集"><a class="anchor" href="#常用工具集"></a>3.2.3. 常用工具集</h4>
<div class="paragraph">
<p><code>Dew</code> 的常用工具由 <code>Dew-Common</code> 包提供（ <a href="https://github.com/gudaoxuri/dew-common" class="bare">https://github.com/gudaoxuri/dew-common</a> ），功能如下：</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>Json与Java对象互转，支持泛型</p>
</li>
<li>
<p>Java Bean操作，Bean复制、反射获取/设置注解、字段、方法等</p>
</li>
<li>
<p>Java Class扫描操作，根据注解或名称过滤</p>
</li>
<li>
<p>Shell脚本操作，Shell内容获取、成功捕获及进度报告等</p>
</li>
<li>
<p>安全（加解密、信息摘要等）操作，Base64、MD5/BCrypt/AES/SHA等对称算法和RSA等非对称算法</p>
</li>
<li>
<p>Http操作，包含Get/Post/Put/Delete/Head/Options/Patch操作</p>
</li>
<li>
<p>金额操作，金额转大写操作</p>
</li>
<li>
<p>通用拦截器栈，前/后置、错误处理等</p>
</li>
<li>
<p>定时器操作，定时和周期性任务</p>
</li>
<li>
<p>常用文件操作，根据不同情况获取文件内容、Glob匹配等</p>
</li>
<li>
<p>常用字段操作，各类字段验证、身份证提取、UUID创建等</p>
</li>
<li>
<p>常用时间处理，常规时间格式化模板</p>
</li>
<li>
<p>主流文件MIME整理，MIME分类</p>
</li>
<li>
<p>服务降级处理</p>
</li>
<li>
<p>脚本处理</p>
</li>
<li>
<p>响应处理及分页模型</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="title"><code>Dew Common</code> 的使用</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">// Dew Common 功能均以 $ 开始，如:

//Json转成Java对象:
$.json.toObject(json,JavaModel.class)
//Json字符串转成List对象
$.json.toList(jsonArray, JavaModel.class)
//Bean复制
$.bean.copyProperties(ori, dist)
//获取Class的注解信息
$.bean.getClassAnnotation(IdxController.class, TestAnnotation.RPC.class)
//非对称加密
$.encrypt.Asymmetric.encrypt(d.getBytes("UTF-8"), publicKey, 1024, "RSA")
//Http Get
$.http.get("https://httpbin.org/get")
//验证手机号格式是否合法
$.field.validateMobile("18657120000")
//...</code></pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
完整使用手册见 <a href="https://gudaoxuri.github.io/dew-common/" class="bare">https://gudaoxuri.github.io/dew-common/</a>
</td>
</tr>
</table>
</div>
</div>
<div class="sect3">
<h4 id="framework-user-manual-cluster"><a class="anchor" href="#framework-user-manual-cluster"></a>3.2.4. 集群功能</h4>
<div class="listingblock">
<div class="content">
<pre>此章节关联示例:cluster-example</pre>
</div>
</div>
<div class="paragraph">
<p><code>Dew</code> 的集群支持 <code>分布式缓存</code> <code>分布式Map</code> <code>分布式锁</code> <code>MQ</code> <code>领导者选举</code>，
并且做了接口抽象以适配不同的实现，目前支持 <code>Redis</code> <code>Hazelcast</code> <code>Rabbit</code> 。</p>
</div>
<div class="paragraph">
<p>各实现对应的支持如下：</p>
</div>
<table class="tableblock frame-all grid-all spread">
<colgroup>
<col style="width: 20%;">
<col style="width: 20%;">
<col style="width: 20%;">
<col style="width: 20%;">
<col style="width: 20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">功能</th>
<th class="tableblock halign-left valign-top">Redis</th>
<th class="tableblock halign-left valign-top">Hazelcast</th>
<th class="tableblock halign-left valign-top">Rabbit</th>
<th class="tableblock halign-left valign-top">MQTT</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">分布式缓存</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">*</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">分布式Map</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">*</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">*</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">分布式锁</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">*</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">*</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">MQ</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">*</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">*</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">*</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">*(只支持pub-sub)</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">领导者选举</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">*</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
<div class="title">各实现的差异</div>
<div class="ulist">
<ul>
<li>
<p>Redis实现了所有功能，但其MQ上不适用于高可用场景</p>
</li>
<li>
<p>只有Rabbit的MQ支持跟踪日志（见跟踪日志章节）</p>
</li>
<li>
<p>MQTT多用于IoT环境</p>
</li>
</ul>
</div>
</td>
</tr>
</table>
</div>
<div class="sect4">
<h5 id="启用方式"><a class="anchor" href="#启用方式"></a>启用方式</h5>
<div class="listingblock">
<div class="title">依赖</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">&lt;dependency&gt;
    &lt;groupId&gt;ms.dew&lt;/groupId&gt;
    &lt;artifactId&gt;boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;!--引入集群依赖，可选redis/hazelcast/rabbit--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;ms.dew&lt;/groupId&gt;
    &lt;artifactId&gt;cluster-spi-redis&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;ms.dew&lt;/groupId&gt;
    &lt;artifactId&gt;cluster-spi-hazelcast&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;ms.dew&lt;/groupId&gt;
    &lt;artifactId&gt;cluster-spi-rabbit&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;ms.dew&lt;/groupId&gt;
    &lt;artifactId&gt;cluster-spi-mqtt&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">增加配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">dew:
    cluster:                            # 集群功能
        cache:                          # 分布式缓存实现，默认为 redis
        map:                            # 分布式Map实现，默认为 redis
        lock:                           # 分布式锁实现，默认为 redis
        mq:                             # MQ实现，默认为 redis
        election:                       # 领导者选举实现，默认为 redis

spring:
    redis:
        host:                           # redis主机
        port:                           # redis端口
        database:                       # redis数据库
        password:                       # redis密码
        lettuce:
          pool:                         # 连接池配置
    rabbitmq:
      host:                             # rabbit主机
      port:                             # rabbit端口
      username:                         # rabbit用户名
      password:                         # rabbit密码
      virtual-host:                     # rabbit VH
    hazelcast:
        addresses: []                   # hazelcast地址，端口可选
dew:
  mw:                                   # 中间件
    mqtt:                               # MQTT集群实现
      broker:                           # Broker地址，e.g. tcp://127.0.0.1:1883
      clientId: Dew_Cluster_&lt;Cluster.instanceId&gt;
                                        # 连接客户端ID，注意一个集群内客户端ID必须唯一
      persistence:                      # 存储，默认为File，可选 memory
      userName:                         # 用户名
      password:                         # 密码
      timeoutSec:                       # 连接超时时间
      keepAliveIntervalSec:             # 心跳间隔时间
      cleanSession: true                # 是否消除Session</code></pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
集群服务的使用入口统一为： <code>Dew.cluster.XX</code>
</td>
</tr>
</table>
</div>
</div>
<div class="sect4">
<h5 id="分布式缓存"><a class="anchor" href="#分布式缓存"></a>分布式缓存</h5>
<div class="listingblock">
<div class="title">API</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">/*
 * Copyright 2019. the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ms.dew.core.cluster;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 缓存服务.
 *
 * @author gudaoxuri
 */
public interface ClusterCache {

    /**
     * key是否存在.
     *
     * @param key key
     * @return 是否存在
     */
    boolean exists(String key);

    /**
     * 获取字符串值.
     *
     * @param key key
     * @return 值
     */
    String get(String key);

    /**
     * 设置字符串值.
     *
     * @param key   key
     * @param value value
     */
    void set(String key, String value);

    /**
     * 设置字符串值.
     *
     * @param key   key
     * @param value value
     */
    void setIfAbsent(String key, String value);

    /**
     * 设置字符串值，带过期时间.
     *
     * @param key       key
     * @param value     value
     * @param expireSec 过期时间(seconds)，0表示永不过期
     */
    void setex(String key, String value, long expireSec);

    /**
     * 字符串不存在时设置值，带过期时间.
     *
     * @param key       key
     * @param value     value
     * @param expireSec 过期时间(seconds)，0表示永不过期
     * @return true 设置成功 , false 设置失败（key已存在）
     */
    boolean setnx(String key, String value, long expireSec);


    /**
     * 设置字符串值，并返回其旧值，不存在时返回null.
     *
     * @param key   key
     * @param value value
     */
    String getSet(String key, String value);

    /**
     * 删除key.
     *
     * @param key key
     */
    void del(String key);

    /**
     * 添加列表值.
     *
     * @param key   key
     * @param value value
     */
    void lpush(String key, String value);

    /**
     * 添加列表.
     *
     * @param key       key
     * @param values    values
     * @param expireSec 过期时间(seconds)，0表示永不过期
     */
    void lmset(String key, List&lt;String&gt; values, long expireSec);

    /**
     * 添加列表.
     *
     * @param key    key
     * @param values values
     */
    void lmset(String key, List&lt;String&gt; values);

    /**
     * 弹出栈顶的列表值.
     * &lt;p&gt;
     * 注意，Redis的列表是栈结构，先进后出
     *
     * @param key key
     * @return 栈顶的列表值
     */
    String lpop(String key);

    /**
     * 获取列表值的长度.
     *
     * @param key key
     * @return 长度
     */
    long llen(String key);

    /**
     * 获取列表中的所有值.
     *
     * @param key key
     * @return 值列表
     */
    List&lt;String&gt; lget(String key);

    /**
     * 添加Set集合.
     *
     * @param key    key
     * @param values values
     */
    void smset(String key, List&lt;String&gt; values);

    /**
     * 添加Set集合.
     *
     * @param key       key
     * @param values    values
     * @param expireSec 过期时间(seconds)，0表示永不过期
     */
    void smset(String key, List&lt;String&gt; values, long expireSec);

    /**
     * 设置Set集合.
     *
     * @param key   key
     * @param value value
     */
    void sset(String key, String value);

    /**
     * 返回一个随机的成员值.
     *
     * @param key key
     * @return 返回值
     */
    String spop(String key);

    /**
     * 获取Set集合的长度.
     *
     * @param key key
     * @return 长度
     */
    long slen(String key);

    /**
     * 删除Set集合对应的values.
     *
     * @param key    key
     * @param values values
     * @return 影响的行数
     */
    long sdel(String key, String... values);

    /**
     * 返回set集合.
     *
     * @param key key
     * @return 值集合
     */
    Set&lt;String&gt; sget(String key);

    /**
     * 设置Hash集合.
     *
     * @param key       key
     * @param items     items
     * @param expireSec 过期时间(seconds)，0表示永不过期
     */
    void hmset(String key, Map&lt;String, String&gt; items, long expireSec);

    /**
     * 设置Hash集合.
     *
     * @param key   key
     * @param items items
     */
    void hmset(String key, Map&lt;String, String&gt; items);


    /**
     * 设置Hash集合field对应的value.
     *
     * @param key   key
     * @param field field
     * @param value value
     */
    void hset(String key, String field, String value);

    /**
     * 设置Hash集合field对应的value.
     *
     * @param key   key
     * @param field field
     * @param value value
     */
    void hsetIfAbsent(String key, String field, String value);


    /**
     * 获取Hash集合field对应的value.
     *
     * @param key   key
     * @param field field
     * @return field对应的value，不存在时返回null
     */
    String hget(String key, String field);

    /**
     * 获取Hash集合的所有items.
     *
     * @param key key
     * @return 所有items
     */
    Map&lt;String, String&gt; hgetAll(String key);

    /**
     * 判断Hash集合field是否存在.
     *
     * @param key   key
     * @param field field
     * @return 是否存在
     */
    boolean hexists(String key, String field);

    /**
     * 获取Hash集合的所有keys.
     *
     * @param key key
     * @return 所有keys
     */
    Set&lt;String&gt; hkeys(String key);

    /**
     * 获取Hash集合的所有values.
     *
     * @param key key
     * @return 所有values
     */
    Set&lt;String&gt; hvalues(String key);

    /**
     * 获取Hash集合的长度.
     *
     * @param key key
     * @return 长度
     */
    long hlen(String key);

    /**
     * 删除Hash集合是对应的field.
     *
     * @param key   key
     * @param field field
     */
    void hdel(String key, String field);

    /**
     * 原子加操作.
     *
     * @param key       key，key不存在时会自动创建值为0的对象
     * @param incrValue 要增加的值，必须是Long Int Float 或 Double
     * @return 操作后的值
     */
    long incrBy(String key, long incrValue);

    /**
     * Hash原子加操作.
     *
     * @param h         h
     * @param hk        hk
     * @param incrValue 要增加的值，必须是Long Int Float 或 Double
     * @return 操作后的值
     */
    long hashIncrBy(String h, String hk, long incrValue);


    /**
     * 原子减操作.
     *
     * @param key       key不存在时会自动创建值为0的对象
     * @param decrValue 要减少的值，必须是Long  或 Int
     * @return 操作后的值
     */
    long decrBy(String key, long decrValue);

    /**
     * 原子减操作.
     *
     * @param h         h
     * @param hk        hk
     * @param decrValue 要减少的值，必须是Long  或 Int
     * @return 操作后的值
     */
    long hashDecrBy(String h, String hk, long decrValue);

    /**
     * 设置过期时间.
     *
     * @param key       key
     * @param expireSec 过期时间(seconds)，0表示永不过期
     */
    void expire(String key, long expireSec);

    /**
     * 获取过期时间（秒）.
     *
     * @param key key
     * @return -2 key不存在，-1 对应的key永不过期，正数 过期时间(seconds)
     */
    long ttl(String key);

    /**
     * 删除当前数据库中的所有Key.
     */
    void flushdb();

    /**
     * 设置bit.
     *
     * @param key    key
     * @param offset offset
     * @param value  值
     * @return 原来的值
     */
    boolean setBit(String key, long offset, boolean value);

    /**
     * 获取指定偏移bit的值.
     *
     * @param key    key
     * @param offset offset
     * @return 指定偏移的值
     */
    boolean getBit(String key, long offset);

}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">// 清空DB
Dew.cluster.cache.flushdb();
// 删除key
Dew.cluster.cache.del("n_test");
// 判断是否存在
Dew.cluster.cache.exists("n_test");
// 设置值
Dew.cluster.cache.set("n_test", "{\"name\":\"jzy\"}", 1);
// 获取值并转成Json
$.json.toJson(Dew.cluster.cache.get("n_test"));</code></pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
Dew的缓存默认只实现了String、List、Set、Hash等结构常用的、时间复杂度低的操作，
如需要的操作Dew没有提供可使用Spring Boot Data Redis原生的<code>RedisTemplate&lt;String,String&gt;</code>
</td>
</tr>
</table>
</div>
<div id="framework-user-manual-cluster-redis-mutli-connection" class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<div class="title">多实例支持</div>
Redis的Cache实现支持多连接实例，目前只支持Lettuce client。
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="content">
<pre># 配置
spring
    redis:
        host:       # Redis主机
        port:       # Redis端口
        ...
        multi:      # &lt;- 多实例支持
          &lt;key&gt;:    # 实例名称
                    # 可用 Dew.cluster.caches.instance(&lt;key&gt;) 获取
                    # 同时可以用 @Autowired &lt;Key&gt;RedisTemplate 获取Bean
            host:   # Redis主机
            port:   # Redis端口
            ...

# 使用
Dew.cluster.caches.instance("&lt;key&gt;").XXX

# 示例
# 使用key为auth的连接
Dew.cluster.caches.instance("auth").set("token", "xxxxx");
# 使用默认连接
Dew.cluster.cache.set("name", "xxxxx")</pre>
</div>
</div>
</div>
<div class="sect4">
<h5 id="分布式map"><a class="anchor" href="#分布式map"></a>分布式Map</h5>
<div class="listingblock">
<div class="title">API</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">/*
 * Copyright 2019. the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ms.dew.core.cluster;

import java.util.Map;
import java.util.function.Consumer;

/**
 * 分布式Map服务.
 *
 * @param &lt;M&gt; 值的类型
 * @author gudaoxuri
 */
public interface ClusterMap&lt;M&gt; {

    /**
     * 添加Item，同步实现.
     *
     * @param key   key
     * @param value value
     */
    void put(String key, M value);

    /**
     * 添加Item，异步实现.
     *
     * @param key   key
     * @param value value
     */
    void putAsync(String key, M value);

    /**
     * 添加不存在的Item，同步实现.
     *
     * @param key   key
     * @param value value
     */
    void putIfAbsent(String key, M value);

    /**
     * 指定Key是否存在.
     *
     * @param key key
     * @return 是否存在 boolean
     */
    boolean containsKey(String key);

    /**
     * 获取所有Item.
     *
     * @return 所有Item all
     */
    Map&lt;String, M&gt; getAll();

    /**
     * 获取指定key的value.
     *
     * @param key key
     * @return 对应的value m
     */
    M get(String key);

    /**
     * 删除指定key的Item，同步实现.
     *
     * @param key key
     */
    void remove(String key);

    /**
     * 删除指定key的Item，异步实现.
     *
     * @param key key
     */
    void removeAsync(String key);

    /**
     * 清空Map.
     */
    void clear();

    /**
     * 注册新增Item时要执行的函数.
     * &lt;p&gt;
     * 目前只支持Hazelcast实现
     *
     * @param fun 执行的函数
     * @return the cluster map
     */
    default ClusterMap&lt;M&gt; regEntryAddedEvent(Consumer&lt;EntryEvent&lt;M&gt;&gt; fun) {
        return this;
    }

    /**
     * 注册删除Item时要执行的函数.
     * &lt;p&gt;
     * 目前只支持Hazelcast实现
     *
     * @param fun 执行的函数
     * @return the cluster map
     */
    default ClusterMap&lt;M&gt; regEntryRemovedEvent(Consumer&lt;EntryEvent&lt;M&gt;&gt; fun) {
        return this;
    }

    /**
     * 注册更新Item时要执行的函数.
     * &lt;p&gt;
     * 目前只支持Hazelcast实现
     *
     * @param fun 执行的函数
     * @return the cluster map
     */
    default ClusterMap&lt;M&gt; regEntryUpdatedEvent(Consumer&lt;EntryEvent&lt;M&gt;&gt; fun) {
        return this;
    }

    /**
     * 注册清空Map时要执行的函数.
     * &lt;p&gt;
     * 目前只支持Hazelcast实现
     *
     * @param fun 执行的函数
     * @return the cluster map
     */
    default ClusterMap&lt;M&gt; regMapClearedEvent(VoidProcessFun fun) {
        return this;
    }

    /**
     * Entry event.
     *
     * @param &lt;V&gt; the type parameter
     */
    class EntryEvent&lt;V&gt; {
        private String key;
        private V oldValue;
        private V value;

        /**
         * Gets key.
         *
         * @return the key
         */
        public String getKey() {
            return key;
        }

        /**
         * Sets key.
         *
         * @param key the key
         */
        public void setKey(String key) {
            this.key = key;
        }

        /**
         * Gets old value.
         *
         * @return the old value
         */
        public V getOldValue() {
            return oldValue;
        }

        /**
         * Sets old value.
         *
         * @param oldValue the old value
         */
        public void setOldValue(V oldValue) {
            this.oldValue = oldValue;
        }

        /**
         * Gets value.
         *
         * @return the value
         */
        public V getValue() {
            return value;
        }

        /**
         * Sets value.
         *
         * @param value the value
         */
        public void setValue(V value) {
            this.value = value;
        }

    }
}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">// 创建指定名为test_obj_map的分布Map实例
ClusterMap&lt;TestMapObj&gt; mapObj = Dew.cluster.map.instance("test_obj_map", TestMapObj.class);
// 清空记录
mapObj.clear();
TestMapObj obj = new TestMapObj();
obj.a = "测试";
// 添加一条记录
mapObj.put("test", obj);
// 获取记录
mapObj.get("test");</code></pre>
</div>
</div>
</div>
<div class="sect4">
<h5 id="分布式锁"><a class="anchor" href="#分布式锁"></a>分布式锁</h5>
<div class="listingblock">
<div class="title">API</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">/*
 * Copyright 2019. the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ms.dew.core.cluster;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 分布式锁服务.
 *
 * @author gudaoxuri
 */
public interface ClusterLock {

    Logger logger = LoggerFactory.getLogger(ClusterLock.class);

    /**
     * 尝试加锁，加锁成功后执行对应的函数，执行完成自动解锁.
     * &lt;p&gt;
     * 推荐使用 {@link #tryLockWithFun(long waitMillSec, long leaseMillSec, VoidProcessFun fun)}
     *
     * @param fun 加锁成功后执行的函数
     */
    void tryLockWithFun(VoidProcessFun fun) throws Exception;

    /**
     * 尝试加锁，加锁成功后执行对应的函数，执行完成自动解锁.
     * &lt;p&gt;
     * 推荐使用 {@link #tryLockWithFun(long waitMillSec, long leaseMillSec, VoidProcessFun fun)}
     *
     * @param fun 加锁成功后执行的函数
     */
    void tryLockWithFun(long waitMillSec, VoidProcessFun fun) throws Exception;

    /**
     * 尝试加锁，加锁成功后执行对应的函数，执行完成自动解锁.
     *
     * @param waitMillSec  等待毫秒数
     * @param leaseMillSec 锁释放毫秒数
     * @param fun          加锁成功后执行的函数
     */
    void tryLockWithFun(long waitMillSec, long leaseMillSec, VoidProcessFun fun) throws Exception;

    /**
     * 尝试加锁.
     * &lt;p&gt;
     * 推荐使用 {@link #tryLock(long waitMillSec, long leaseMillSec)}
     */
    boolean tryLock();

    /**
     * 尝试加锁.
     * &lt;p&gt;
     * 推荐使用 {@link #tryLock(long waitMillSec, long leaseMillSec)}
     *
     * @param waitMillSec 等待毫秒数
     */
    boolean tryLock(long waitMillSec) throws InterruptedException;

    /**
     * 尝试加锁.
     *
     * @param waitMillSec  等待毫秒数
     * @param leaseMillSec 锁释放毫秒数，估计值，实际释放时间视各中间件的配置及执行环境会有一定偏差
     */
    boolean tryLock(long waitMillSec, long leaseMillSec) throws InterruptedException;

    /**
     * 解锁操作，只有加锁的实例及线程才能解锁.
     */
    boolean unLock();

    /**
     * 强制解锁，不用匹配加锁的实例与线程.
     * &lt;p&gt;
     * 谨慎使用
     */
    void delete();

    /**
     * 判断是否有锁.
     */
    boolean isLocked();

}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">// 创建指定名为test_lock的分布锁实例
ClusterLock lock = Dew.cluster.lock.instance("test_lock");
// tryLock 示例，等待0ms，忘了手工unLock或出异常时1s后自动解锁
if (lock.tryLock(0, 1000)) {
    try {
        // 已加锁，执行业务方法
    } finally {
        // 手工解锁
        lock.unLock();
    }
}
// 上面的示例可用 tryLockWithFun 简化
lock.tryLockWithFun(0, 1000, () -&gt; {
    // 已加锁，执行业务方法，tryLockWithFun会将业务方法包裹在try-finally中，无需手工解锁
});</code></pre>
</div>
</div>
</div>
<div class="sect4">
<h5 id="mq"><a class="anchor" href="#mq"></a>MQ</h5>
<div class="listingblock">
<div class="title">API</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">/*
 * Copyright 2019. the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ms.dew.core.cluster;

import ms.dew.core.cluster.dto.MessageWrap;

import java.util.Map;
import java.util.function.Consumer;

/**
 * MQ服务.
 *
 * @author gudaoxuri
 */
public interface ClusterMQ {

    /**
     * MQ 发布订阅模式 之 发布.
     * &lt;p&gt;
     * 请确保发布之前 topic 已经存在
     *
     * @param topic   主题
     * @param message 消息内容
     * @param header  消息头
     * @param confirm 是否需要确认
     * @return 是否发布成功 ，此返回值仅在 confirm 模式下才能保证严格准确！
     */
    boolean publish(String topic, String message, Map&lt;String, Object&gt; header, boolean confirm);

    /**
     * MQ 发布订阅模式 之 发布.
     * &lt;p&gt;
     * 请确保发布之前 topic 已经存在
     *
     * @param topic   主题
     * @param message 消息内容
     * @param header  消息头
     */
    default void publish(String topic, String message, Map&lt;String, Object&gt; header) {
        publish(topic, message, header, false);
    }

    /**
     * MQ 发布订阅模式 之 发布.
     * &lt;p&gt;
     * 请确保发布之前 topic 已经存在
     *
     * @param topic   主题
     * @param message 消息内容
     */
    default void publish(String topic, String message) {
        publish(topic, message, null);
    }


    /**
     * MQ 发布订阅模式 之 订阅.
     * &lt;p&gt;
     * 非阻塞方式
     *
     * @param topic    主题
     * @param consumer 订阅处理方法
     */
    void subscribe(String topic, Consumer&lt;MessageWrap&gt; consumer);

    /**
     * MQ 请求响应模式 之 请求.
     *
     * @param address 请求地址
     * @param message 消息内容
     * @param header  消息头
     * @param confirm 是否需要确认
     * @return 是否发布成功 ，此返回值仅在 confirm 模式下才能保证严格准确！
     */
    boolean request(String address, String message, Map&lt;String, Object&gt; header, boolean confirm);

    /**
     * MQ 请求响应模式 之 请求.
     *
     * @param address 请求地址
     * @param message 消息内容
     * @param header  消息头
     */
    default void request(String address, String message, Map&lt;String, Object&gt; header) {
        request(address, message, header, false);
    }


    /**
     * MQ 请求响应模式 之 请求.
     *
     * @param address 请求地址
     * @param message 消息内容
     */
    default void request(String address, String message) {
        request(address, message, null);
    }


    /**
     * MQ 请求响应模式 之 响应.
     * &lt;p&gt;
     * 非阻塞方式
     *
     * @param address  请求对应的地址
     * @param consumer 响应处理方法
     */
    void response(String address, Consumer&lt;MessageWrap&gt; consumer);

    /**
     * Gets mq header.
     *
     * @param name the name
     * @return the mq header
     */
    default Map&lt;String, Object&gt; getMQHeader(String name) {
        return Cluster.getMQHeader(name);
    }

    /**
     * Sets mq header.
     *
     * @param name   the name
     * @param header the header
     * @return the mq header
     */
    default Map&lt;String, Object&gt; setMQHeader(String name, Map&lt;String, Object&gt; header) {
        return Cluster.setMQHeader(name, header);
    }

    /**
     * Is header support.
     *
     * @return the result
     */
    boolean supportHeader();
}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">// pub-sub
Dew.cluster.mq.subscribe("test_pub_sub", message -&gt;
        logger.info("pub_sub&gt;&gt;" + message.getBody()));
Dew.cluster.mq.publish("test_pub_sub", "msgA",new HashMap&lt;String, Object&gt;() {
      {
          put("h", "001");
      }
  });
Dew.cluster.mq.publish("test_pub_sub", "msgB");
// req-resp
Dew.cluster.mq.response("test_rep_resp", message -&gt;
        logger.info("req_resp&gt;&gt;" + message.getBody()));
Dew.cluster.mq.request("test_rep_resp", "msg1",new HashMap&lt;String, Object&gt;() {
      {
          put("h", "001");
      }
  });
Dew.cluster.mq.request("test_rep_resp", "msg2");</code></pre>
</div>
</div>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
发布订阅模式时，发布前 <code>topic</code> 必须已经存在，可先使用 <code>subscribe</code> 订阅，此操作会自动创建 <code>topic</code> 。
</td>
</tr>
</table>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<code>Rabbit</code> 实现支持单条 <code>confirm</code> 模式。
</td>
</tr>
</table>
</div>
<div class="paragraph">
<div class="title">MQ的HA功能</div>
<p>MQ的HA（高可用）支持，默认为禁用，可通过<code>dew.cluster.config.ha-enabled=true</code>启用。</p>
</div>
<div class="paragraph">
<p>Dew的MQ仅在数据处理完成后才做commit，这限制了对同一个队列只能串行处理，
MQ的HA开启后，您可以以多线程的方式消费消息，处理过程中如发生服务宕机重启后仍可从未处理完成的消息开始消费。</p>
</div>
</div>
<div class="sect4">
<h5 id="领导者选举"><a class="anchor" href="#领导者选举"></a>领导者选举</h5>
<div class="listingblock">
<div class="title">API</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">/*
 * Copyright 2019. the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ms.dew.core.cluster;

/**
 * 领导者选举服务.
 *
 * @author gudaoxuri
 */
public interface ClusterElection {

    /**
     * 当前工程是否是领导者.
     *
     * @return 是否是领导者
     */
    boolean isLeader();

}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">// 实例化fun1类型的领导者选举，Redis的实现支持多类型领导者
ClusterElection electionFun1 = Dew.cluster.election.instance("fun1");
// ...
if (electionFun1.isLeader()) {
   // 当前节点是fun1类型的领导者
   // ...
}</code></pre>
</div>
</div>
</div>
</div>
<div class="sect3">
<h4 id="统一响应"><a class="anchor" href="#统一响应"></a>3.2.5. 统一响应</h4>
<div class="paragraph">
<p><code>Dew</code> 推荐使用 <code>协议无关的响应格式</code>，此格式在 <code>方法间调用</code> <code>非HTTP协议RPC</code> <code>MQ</code> 等数据交互场景做到真正的 <code>统一响应格式</code>。
要求返回的格式为<code>Resp</code>对象，格式为：</p>
</div>
<div class="listingblock">
<div class="content">
<pre>{
    "code": "", // 响应编码，与http状态码类似，200表示成功
    "message":"", // 响应附加消息，多有于错误描述
    "body": // 响应正文
}</pre>
</div>
</div>
<div class="listingblock">
<div class="title">示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">public Resp&lt;String&gt; test(){
    return Resp.success("enjoy!");
    // OR return Resp.notFound("…")/conflict("…")/badRequest("…")/…
}</code></pre>
</div>
</div>
<div class="paragraph">
<p><code>Resp</code>类提供了常用操作：详见 <a href="https://gudaoxuri.github.io/dew-common/#true-resp">https://gudaoxuri.github.io/dew-common/#true-resp</a></p>
</div>
<div class="paragraph">
<p><code>Dew</code>使用返回格式中的code表示操作状态码，此状态码与HTTP状态码无关，一般情况下HTTP状态码均为200，如需要降级处理时返回500。</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="title">500 Http状态码说明</div>
<div class="paragraph">
<p><code>500</code> 状态码仅用于告诉 <code>Hystrix</code> 或其它熔断器这次请求是需要降级的错误，对于 <code>Resp</code> 中的 <code>code</code> 没有影响。</p>
</div>
<div class="paragraph">
<p><code>dew</code> 框架会把所有 <code>5xx</code>（服务端错误，需要降级） 的异常统一转换成 <code>500</code> 的Http状态码返回给调用方。</p>
</div>
<div class="paragraph">
<p><code>Resp.xxx.fallback()</code> 用于显式声明当前返回需要降级，
比如 <code>Resp.serverError("some message")</code> 不会降级，返回http状态码为200，body为 <code>{"code":"500","message":"some message","body":null}</code>，
但 <code>Resp.serverError("some message").fallback()</code> 会降级，返回http状态码为500，body为 同上。</p>
</div>
</td>
</tr>
</table>
</div>
<div class="sect4">
<h5 id="消息通知"><a class="anchor" href="#消息通知"></a>消息通知</h5>
<div class="paragraph">
<p><code>Dew</code> 支持发送消息到钉钉、邮件或自定义HTTP地址，默认支持对未捕获异常及Hystrix错误的通知。</p>
</div>
<div class="listingblock">
<div class="title">通知配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml"># 格式
dew:
  notifies:
    "": # 通知的标识
      type: DD # 通知的类型，DD=钉钉 MAIL=邮件，邮件方式需要有配置spring.mail下相关的smtp信息 HTTP=自定义HTTP Hook
      defaultReceivers: # 默认接收人列表，钉钉为手机号，邮件为邮箱
      dndTimeReceivers: # 免扰时间内的接收人列表，只有该列表中的接收人才能在免扰时间内接收通知
      args: # 不同类型的参数，邮件不需要设置
        url: # type=DD表示钉钉的推送地址
             # 说明详见：https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq
             # type=HTTP表示HTTP Hook的地址
        msgType: # 仅用于type=DD，支持 text/markdown            strategy: # 通知策略
        minIntervalSec: 0 # 最小间隔的通知时间，0表示不设置，如为10则表示10s内只会发送一次
        dndTime: # 开启免扰时间，HH:mm-HH:mm 如，18:00-06:00
        forceSendTimes: 3 # 同一免扰周期间通知调用达到几次后强制发送

# 示例
dew:
  notifies:
    __DEW_ERROR__:
      type: DD
      defaultReceivers: xxxx
      args:
        url: https://oapi.dingtalk.com/robot/send?access_token=8ff65c48001c1981df7d3269
      strategy:
        minIntervalSec: 5
    sendMail:
      type: MAIL
      defaultReceivers: x@y.z
    custom:
      type: HTTP
      defaultReceivers: x@y.z
      args:
        url: https://...</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">通知使用</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java"># 最简单的调用
Resp&lt;Void&gt; result = Dew.notify.send("&lt;通知的标识&gt;", "&lt;通知的内容或Throwable&gt;");
# 带通知标题，标题会加上``Dew.Info.instance``
Resp&lt;Void&gt; result = Dew.notify.send("&lt;通知的标识&gt;", "&lt;通知的内容或Throwable&gt;", "&lt;通知标题&gt;");
# 加上特殊接收人列表，非免扰时间内的接收人=配置默认接收人列表+特殊接收人列表，免扰时间内的接收人=配置的免扰时间内的接收人列表
Resp&lt;Void&gt; result = Dew.notify.send("&lt;通知的标识&gt;", "&lt;通知的内容或Throwable&gt;", "&lt;通知标题&gt;", "&lt;特殊接收人列表&gt;");
# 上述三个方法都有异步的重载方法，如
Dew.notify.sendAsync("&lt;通知的标识&gt;", "&lt;通知的内容或Throwable&gt;");</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="title">默认通知标识</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>未捕获异常: <code><em>DEW_ERROR</em></code>，所有未捕获异常（ErrorController）调用此标识发送错误，可通过<code>dew.basic.format.error-flag</code> 修改</p>
</li>
<li>
<p>Hystrix错误: <code><em>HYSTRIX</em></code>，所有Hystrix错误调用此标识发送错误，可通过<code>dew.cloud.error.notify-flag</code> 修改</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>要启用以上两个通知请确保<code>dew.notifies</code>下有相应的配置。</p>
</div>
</td>
</tr>
</table>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="title">HTTP自定义通知格式</div>
<div class="paragraph">
<p>POST请求，Body格式为:</p>
</div>
<div class="paragraph">
<p>{
    "title": "", // 标题
    "content": "", // 内容
    "receivers": [] // 接收人列表
}</p>
</div>
<div class="paragraph">
<p>调用正常需要返回200状态码</p>
</div>
</td>
</tr>
</table>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
消息通知由 <code>notification</code> 模块提供，<code>boot-starter</code> 集成了此模块，开发中也可以单独引用 <code>notification</code>。
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect3">
<h4 id="异常处理"><a class="anchor" href="#异常处理"></a>3.2.6. 异常处理</h4>
<div class="paragraph">
<p><code>Dew</code> 会把程序没有捕获的异常统一上抛，同时框架也支持上文统一响应的异常处理：</p>
</div>
<div class="listingblock">
<div class="title">自定义异常以支持统一响应API</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">/**
* 统一异常处理.
* &lt;p&gt;
* 封装任意异常到统一的格式，见下文
*
* @param &lt;E&gt;            上抛的异常类型
* @param code           异常编码
* @param ex             上抛的异常对象
* @param customHttpCode 自定义Http状态码
* @return 上抛的异常对象
*/
Dew.E.e(String code, E ex, int customHttpCode)</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">自定义异常以支持统一响应示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">// 业务代码捕获了一个异常
Exception someError = new IOException("xxx不存在")
// 使用统一异常处理封装
throw Dew.E.e("NBE00123",someError,200)
// 请求方得到的结果为 http状态=200，响应体：
{
    "code": "NBE00123",
    "message": "xxx不存在",
    "body": null
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>上面介绍的是编码的方式将某些异常封装处理，我们也可以用配置解决：</p>
</div>
<div class="listingblock">
<div class="title">自定义异常配置，启用后此类异常均使用此模块</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">dew:
  basic:
    error-mapping:
      "[&lt;异常类名&gt;]":
        http-code: # http状态码，不存在时使用实例级http状态码
        business-code: # 业务编码，不存在时使用实例级业务编码
        message: # 错误描述，不存在时使用实例级错误描述</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">自定义异常配置示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">dew:
  basic:
    error-mapping:
      "java.io.IOException":
        http-code: 200
        business-code: "NBE00123"</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="数据验证"><a class="anchor" href="#数据验证"></a>3.2.7. 数据验证</h4>
<div class="paragraph">
<p><code>Dew</code>集成了<code>Spring validate</code> 机制，支持针对 <code>URL</code> 及 <code>Bean</code> 的验证。</p>
</div>
<div class="ulist">
<ul>
<li>
<p>在 java bean 中添加各项validation，支持标准<code>javax.validation.constraints</code>包下的诸如：<code>NotNull</code> ，同时框架扩展了几个检查，如：
IdNumber、Phone</p>
</li>
<li>
<p>在Controller中添加 <code>@Validated</code> 注解 ( Spring还支持@Vaild，但这一注解不支持分组 )</p>
</li>
<li>
<p>支持Spring原生分组校验</p>
</li>
<li>
<p><code>URL</code> 类型的验证必须在类头添加 <code>@Validated</code> 注解</p>
</li>
<li>
<p><code>Dew</code> 框架内置了 <code>CreateGroup</code> <code>UpdateGroup</code> 两个验证组，验证组仅是一个标识，可为任何java对象</p>
</li>
</ul>
</div>
<div class="listingblock">
<div class="title">数据验证示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">@RestController
@Api(value = "测试")
@RequestMapping(value = "/test/")
@Validated
public class WebController {

    /**
     * Valid create user dto.
     *
     * @param userDTO the user dto
     * @return the user dto
     */
    @PostMapping(value = "valid-create")
    public UserDTO validCreate(@Validated(CreateGroup.class) @RequestBody UserDTO userDTO) {
        return userDTO;
    }

    /**
     * Valid update user dto.
     *
     * @param userDTO the user dto
     * @return the user dto
     */
    @PostMapping(value = "valid-update")
    public UserDTO validUpdate(@Validated(UpdateGroup.class) @RequestBody UserDTO userDTO) {
        return userDTO;
    }

    /**
     * Valid in method.
     *
     * @param age the age
     * @return the string
     */
    @GetMapping(value = "valid-method-spring/{age}")
    public String validInMethod(@Min(value = 2, message = "age必须大于2") @PathVariable("age") int age) {
        return String.valueOf(age);
    }

    /**
     * Valid in method.
     *
     * @param phone the phone
     * @return the string
     */
    @GetMapping(value = "valid-method-own/{phone}")
    public String validInMethod(@Phone @PathVariable("phone") String phone) {
        return phone;
    }

    public static class UserDTO {

        @NotNull(groups = CreateGroup.class)
        @IdNumber(message = "身份证号错误", groups = {CreateGroup.class, UpdateGroup.class})
        private String idCard;

        @Min(value = 10, groups = {CreateGroup.class, UpdateGroup.class})
        private Integer age;

        @NotNull(groups = CreateGroup.class)
        @Phone(message = "手机号错误", groups = {CreateGroup.class, UpdateGroup.class})
        private String phone;

        // Get &amp; Set ...
    }

}</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="cors支持"><a class="anchor" href="#cors支持"></a>3.2.8. CORS支持</h4>
<div class="listingblock">
<div class="title">配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">dew:
  security:
    cors:
      allow-origin: # 允许来源，默认 *
      allow-methods: # 允许方法，默认 POST,GET,OPTIONS,PUT,DELETE,HEAD
      allow-headers: # 允许头信息 x-requested-with,content-type</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="framework-user-manual-auth"><a class="anchor" href="#framework-user-manual-auth"></a>3.2.9. 权限认证</h4>
<div class="listingblock">
<div class="content">
<pre>此章节关联示例:auth-example</pre>
</div>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>支持 <code>认证缓存</code> ，即支持将鉴权系统生成的登录信息缓存到业务系统中方便即时调用，并提供三方适配。</p>
</div>
</blockquote>
</div>
<div class="listingblock">
<div class="title">配置认证缓存</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">dew:
  security:
    token-flag: X-Dew-Token             # Token 标识
    token-kind-flag: X-Dew-Token-Kind   # Token类型 标识
    token-in-header: true               # true：token标识在 header 中，反之在url参数中
    token-hash: false                   # Token值是否需要hash，用于解决token值有特殊字符的情况
    router:                             # 路由功能
      enabled: false                    # 是否启用
      black-uri:                        # URL黑名单，支持ant风格
        &lt;Http Method&gt;: [&lt;URLs&gt;]         # URL黑名单列表
    token-kinds:                        # Token类型，可为不同的Token类型设置不同的过期时间、保留的版本
      &lt;Kind&gt;:                           # Token类型名称，比如 PC/Android/... ，默认会创建名为 DEFAULT 的类型
        expire-sec: 86400               # Token 过期时间（秒）
        revision-history-limit: 0       # Token 保留的历史版本数量， 0表示不保留历史版本，即有新的登录时会删除此类型下之前所有的Token</code></pre>
</div>
</div>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
认证缓存需要 <code>集群缓存</code> 服务支持，请引入相关的依赖并配置对应的连接信息等。
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">basic 认证缓存接口</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">// 添加登录信息，optInfo封装自鉴权系统过来的登录信息
// 一般在登录认证后操作
Dew.auth.setOptInfo(OptInfo optInfo);
// 获取登录信息，要求在http请求加上token信息
Dew.context().optInfo();
// 删除登录信息
// 一般在注销登录OptInfo后操作
Dew.auth.removeOptInfo();

// 登录信息
public class OptInfo {
    // Token
    String token;
    // 账号编码
    String accountCode;
}</code></pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<code>OptInfo</code> 为认证缓存信息的基类，使用时可以继承并扩展自己的属性。
</td>
</tr>
</table>
</div>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
使用 <code>OptInfo</code> 扩展类型时需要在工程启动时指定扩展类： <code>DewContext.setOptInfoClazz(&lt;扩展类型&gt;)</code> 。
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">basic 认证缓存示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">/**
 * 模拟用户注册.
 */
@PostMapping(value = "user/register")
public Resp&lt;Void&gt; register(@RequestBody User user) {
    // 实际注册处理
    user.setId($.field.createUUID());
    MOCK_USER_CONTAINER.put(user.getId(), user);
    return Resp.success(null);
}

/**
 * 模拟用户登录.
 */
@PostMapping(value = "auth/login")
public Resp&lt;String&gt; login(@RequestBody LoginDTO loginDTO) {
    // 实际登录处理
    User user = MOCK_USER_CONTAINER.values().stream().filter(u -&gt; u.getIdCard().equals(loginDTO.getIdCard())).findFirst().get();
    String token = $.field.createUUID();
    Dew.auth.setOptInfo(new OptInfoExt()
            .setIdCard(user.getIdCard())
            .setAccountCode($.field.createShortUUID())
            .setToken(token)
            .setName(user.getName())
            .setMobile(user.getPhone()));
    return Resp.success(token);
}

/**
 * 模拟业务操作.
 */
@GetMapping(value = "business/someopt")
public Resp&lt;Void&gt; someOpt() {
    // 获取登录用户信息
    Optional&lt;OptInfoExt&gt; optInfoExtOpt = Dew.auth.getOptInfo();
    if (!optInfoExtOpt.isPresent()) {
        return Resp.unAuthorized("用户认证错误");
    }
    // 登录用户的信息
    optInfoExtOpt.get();
    return Resp.success(null);
}

/**
 * 模拟用户注销.
 */
@DeleteMapping(value = "auth/logout")
public Resp&lt;Void&gt; logout() {
    // 实际注册处理
    Dew.auth.removeOptInfo();
    return Resp.success(null);
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>上述操作的核心是认证适配器，其接口如下：</p>
</div>
<div class="listingblock">
<div class="title">AuthAdapter</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">/*
 * Copyright 2019. the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ms.dew.core.auth;

import ms.dew.Dew;
import ms.dew.core.auth.dto.OptInfo;

import java.util.Optional;

/**
 * 登录鉴权适配器.
 *
 * @author gudaoxuri
 */
public interface AuthAdapter {

    /**
     * 获取当前登录的操作用户信息.
     *
     * @param &lt;E&gt; 扩展操作用户信息类型
     * @return 操作用户信息
     */
    default &lt;E extends OptInfo&gt; Optional&lt;E&gt; getOptInfo() {
        return Dew.context().optInfo();
    }

    /**
     * 根据Token获取操作用户信息.
     *
     * @param &lt;E&gt;   扩展操作用户信息类型
     * @param token 登录Token
     * @return 操作用户信息
     */
    &lt;E extends OptInfo&gt; Optional&lt;E&gt; getOptInfo(String token);

    /**
     * 设置操作用户信息类型.
     *
     * @param &lt;E&gt;     扩展操作用户信息类型
     * @param optInfo 扩展操作用户信息
     */
    &lt;E extends OptInfo&gt; void setOptInfo(E optInfo);

    /**
     * 删除当前登录的操作用户信息.
     * &lt;p&gt;
     * 对指注销登录
     */
    default void removeOptInfo() {
        getOptInfo().ifPresent(optInfo -&gt; removeOptInfo(optInfo.getToken()));
    }

    /**
     * 根据Token删除操作用户信息.
     *
     * @param token 登录Token
     */
    void removeOptInfo(String token);
}</code></pre>
</div>
</div>
<div class="paragraph">
<p><code>Dew</code> 默认实现了基于 <code>Dew.cluster.cache</code> 的适配器以支持上述功能，
对于Redis的Cache实现了 <a href="#framework-user-manual-cluster-redis-mutli-connection">多实例支持</a> ，默认优先获取key = <code><em>auth</em></code> 的连接配置，不存在时使用默认连接配置。
，项目中也可以实现自己的适配器。</p>
</div>
<div class="listingblock">
<div class="title">自定义认证适配器</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">// 自定义适配器
public class CustomAuthAdapter implements AuthAdapter {

 // ...

}

// 注册为Bean（可选，如果自定义适配器用到Spring功能时必须）
@Bean
public CustomAuthAdapter customAuthAdapter() {
    return new CustomAuthAdapter();
}

// 注册自定义适配器
Dew.auth = new CustomAuthAdapter() // 或 Bean实例</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="测试支持"><a class="anchor" href="#测试支持"></a>3.2.10. 测试支持</h4>
<div class="paragraph">
<p>良好的单元测试可以保证代码的高质量，单测的重要原则是内聚、无依赖，好的单测应该是"函数化"的——结果的变化只与传入参数有关。
但实际上我们会的代码往往会与数据库、缓存、MQ等外部工具交互，这会使单测的结果不可控，通常的解决方案是使用Mock，但这无行中引入了单测撰写的成本，
<code>Dew</code>使用"内嵌式"工具解决，数据库使用 <code>H2</code> ，Redis使用 <code>embedded redis</code> ，由于 <code>Dew</code> 集群的 <code>Cache</code> <code>Map</code> <code>Lock</code> <code>MQ</code> 都支持 <code>Redis</code> 实现，所以可以做到对主流操作的全覆盖。</p>
</div>
<div class="listingblock">
<div class="title">依赖</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">&lt;dependency&gt;
    &lt;groupId&gt;ms.dew&lt;/groupId&gt;
    &lt;artifactId&gt;test-starter&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">dew:
  cluster: #所有集群操作都使用reids模拟
    cache: redis
    lock: redis
    map: redis
    mq: redis

spring:
  redis:
    host: 127.0.0.1
    port: 6379
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:test</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="幂等处理"><a class="anchor" href="#幂等处理"></a>3.2.11. 幂等处理</h4>
<div class="listingblock">
<div class="content">
<pre>此章节关联示例:idempotent-example</pre>
</div>
</div>
<div class="paragraph">
<p>支持HTTP和非HTTP幂等操作，对于HTTP操作，要求请求方在请求头或URL参数中加上操作ID标识，非HTTP操作由可自由指定操作类型和操作ID标识的来源。</p>
</div>
<div class="listingblock">
<div class="title">依赖</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">&lt;!--引入幂等支持--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;ms.dew&lt;/groupId&gt;
    &lt;artifactId&gt;idempotent-starter&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">dew:
  cluster:
    cache: redis # 启用Redis支持
  idempotent:
    default-expire-ms: 3600000 # 设置默认过期时间，1小时
    default-strategy: item # 设置默认策略，支持 bloom(Bloom Filter)和item(逐条记录)，目前只支持item
    default-opt-id-flag: __IDEMPOTENT_OPT_ID__ # 指定幂等操作ID标识，可以位于HTTP Header或请求参数中</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">HTTP操作</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">@GetMapping(xxx)
// 启用幂等支持
// 请求头部或参数加上__IDEMPOTENT_OPT_ID__ = xx
@Idempotent
public void test(xxx) {
    // 业务操作
    // ...
    // 业务失败，在保证业务操作的原子性的情况下，在catch中取消幂等，并抛出异常
    DewIdempotent.cancel();
    // 手工确认
    DewIdempotent.confirm();
}</code></pre>
</div>
</div>
<div class="paragraph">
<p><code>Idempotent</code>注解说明：</p>
</div>
<div class="ulist">
<ul>
<li>
<p>optIdFlag：指定幂等操作ID标识，可以位于HTTP Header或请求参数中</p>
</li>
<li>
<p>expireMs：设置过期时间，单位毫秒</p>
</li>
<li>
<p>strategy：设置默认策略</p>
</li>
<li>
<p>needConfirm：设置是否需要显式确认，true时，需要进行显式确认操作: <code>DewIdempotent.confirm() 或 DewIdempotent.confirm(String optType, String optId)</code> 前者要求与请求入口在同一线程中</p>
</li>
</ul>
</div>
<div class="listingblock">
<div class="title">非HTTP操作</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">// 初始化类型为transfer_a的幂等操作，需要手工确认，过期时间为1秒
DewIdempotent.initOptTypeInfo("transfer_a", true, 1000, StrategyEnum.ITEM);
// 第一次请求transfer_a类型下的xxxxxxx这个ID，返回不存在，表示可以下一步操作
Assert.assertEquals(StatusEnum.NOT_EXIST, DewIdempotent.process("transfer_a", "xxxxxxx"));
// 第二次请求transfer_a类型下的xxxxxxx这个ID，返回未确认，表示上次操作还在进行中
Assert.assertEquals(StatusEnum.UN_CONFIRM, DewIdempotent.process("transfer_a", "xxxxxxx"));
// 确认操作完成
DewIdempotent.confirm("transfer_a", "xxxxxxx");
// 第三次请求transfer_a类型下的xxxxxxx这个ID，返回已确认，但未过期，仍不能操作
Assert.assertEquals(StatusEnum.CONFIRMED, DewIdempotent.process("transfer_a", "xxxxxxx"));
// 延时1秒
Thread.sleep(1000);
// 再次请求transfer_a类型下的xxxxxxx这个ID，返回不存在（上次请求已过期），表示可以下一步操作
Assert.assertEquals(StatusEnum.NOT_EXIST, DewIdempotent.process("transfer_a", "xxxxxxx"));</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="api文档"><a class="anchor" href="#api文档"></a>3.2.12. API文档</h4>
<div class="listingblock">
<div class="title">配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">dew:
  basic:
    name: # 文档名称
    version: 1.0 # 文档版本
    desc: # 文档说明
    web-site: # 文档站点
    doc:
      base-package: # API文档要扫描的根包，多指定到 Controller 包中
      contact: # 联系人信息，可为空
        name: # 联系人姓名
        url: # 联系人URL
        email: # 联系人邮箱

management: # 文档生成地址前缀为 /management.endpoints.web.base-path
  endpoints:
    web:
      base-path: /management # 可修改成自定义路径，默认为 actuator</code></pre>
</div>
</div>
<div class="paragraph">
<p>在线API文档请访问 <code>http(s)://&lt;host&gt;:&lt;port&gt;/doc.html</code></p>
</div>
<div class="paragraph">
<p>离线API文档会生成Asciidoc格式，可通过软件转换成HTML及PDF版本。</p>
</div>
<div class="listingblock">
<div class="title">使用</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-txt" data-lang="txt">访问任意服务实例:

PUT /{management.endpoints.web.base-path}/doc/offline

{
	"docName":"", # 文档名称
	"docDesc":"", # 文档说明
	"visitUrls":{ # 不同环境的访问地址
		"":""
	},
	"swaggerJsonUrls":[] # swagger.json的URL列表(http://.../v2/api-docs)，可选
}

swaggerJsonUrls 不为空时根据传入的swaggerJsonUrls合成接口文档，为空时分两种情况：

1. 如果是非集群模式（没有注册中心）则只生成本服务的接口文档
2. 如果是集群模式（注册到注册中心）则会合成此集群下所有的接口文档

示例：

{
	"docName":"Dew API 文档",
	"docDesc":"此为Dew的接口文档\n描述可以换行",
	"visitUrls":{
		"开发环境":"http://dev.dew.ecfront.com",
		"测试环境":"http://test.dew.ecfront.com"
	}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>调用此接口后返回Asciidoc格式的文档内容，复制此内容到相应的转换工具中即可生成对应的HTTP或PDF文档。</p>
</div>
</div>
<div class="sect3">
<h4 id="代码质量检查"><a class="anchor" href="#代码质量检查"></a>3.2.13. 代码质量检查</h4>
<div class="paragraph">
<p><code>Dew</code> 已集成 <code>Sonar</code> 插件，只需要在maven中配置 <code>sonar.host.url</code> 为目标地址，
然后执行 <code>mvn clean verify sonar:sonar -P qa -Dsonar.login=&lt;用户名&gt; -Dsonar.password=&lt;密码&gt;</code> 即可。</p>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
也可以设置 <code>sonar.forceAuthentication=false</code> ，但要注意安全管控。
</td>
</tr>
</table>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
使用 <code>&lt;maven.test.skip&gt;true&lt;/maven.test.skip&gt;</code> 可跳过特定模块的测试，<code>&lt;sonar.skip&gt;true&lt;/sonar.skip&gt;</code> 可跳过特定模块的Sonar检查。
</td>
</tr>
</table>
</div>
<div class="sect4">
<h5 id="spring_boot_hbase"><a class="anchor" href="#spring_boot_hbase"></a>Spring Boot HBase</h5>
<div class="paragraph">
<p>在集成 HBase 客户端能力的基础之上，支持 Spring Boot 配置管理、支持 Kerberos 认证。</p>
</div>
<div class="listingblock">
<div class="title">依赖</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">&lt;dependency&gt;
    &lt;groupId&gt;ms.dew&lt;/groupId&gt;
    &lt;artifactId&gt;hbase-starter&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">spring:
  hbase:
    zkQuorum: localhost  # zookeeper url
    znodeParent: /hbase-secure # zookeeper znode parent
    auth:
      type: kerberos # 认证类型，默认是 simple，可选：simple 和 kerberos
      principal: # kerberos 下 principal
      keytab: # kerberos 下 keytab 路径
      hbaseMasterPrincipal: # kerberos 下 hbase master principal
      hbaseRegionServerPrincipal: # kerberos 下 hbase region server principal
      hbaseClientRetriesNumber: # hbase 客户端重试次数，默认：5
      hbaseClientOperationTimeout: # hbase 客户端超时时间，默认：300000
      hbaseClientScannerTimeoutPeriod: # hbase 客户端 scan 超时时间，默认：60000
      hbaseClientPause: # hbase 重试的休眠时间，默认：30</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">使用</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">@Autowired
private HBaseTemplate hbaseTemplate;

hbaseTemplate.get("table_hbase", "0002093140000000",
                "0", "reg_platform", (result, row) -&gt; Bytes.toString(result.value()));</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
HBaseTemplate 其他使用方法可以详见 hbase-starter 模块下的 test 内容。
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="framework-configuration"><a class="anchor" href="#framework-configuration"></a>3.3. 框架配置速查</h3>
<div class="sect3">
<h4 id="code_dew_code_参数"><a class="anchor" href="#code_dew_code_参数"></a>3.3.1. <code>Dew</code> 参数</h4>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">dew:                                    # Dew 参数key前缀
  basic:                                # 基础配置
    name:                               # 服务名称，用于API文档显示等
    version: 1.0                        # 服务版本号，用于API文档显示等
    desc:                               # 服务描述，用于API文档显示等
    webSite:                            # 官网，用于API文档显示等
    doc:                                # 文档配置
      enabled: true                     # 是否启用默认文档配置，关闭后可自定义文档管理，默认为true
      base-package:                     # API文档要扫描的根包，多指定到 Controller 包中
      contact:                          # 联系人信息
        name:                           # 联系人姓名
        url:                            # 联系人URL
        email:                          # 联系人邮箱
    format:                             # 格式化配置
      use-unity-error: true             # 是否启用统一响应
      error-flag: __DEW_ERROR__         # 默认的通知标识
    error-mapping:                      # 自定义错误映射
      "[&lt;&gt;]":                           # 异常类名
        http-code:                      # http状态码，不存在时使用实例级http状态码
        business-code:                  # 业务编码，不存在时使用实例级业务编码
        message:                        # 错误描述，不存在时使用实例级错误描述
  cluster:                              # 集群功能
    cache: redis                        # 缓存实现
    lock: redis                         # 分布式锁实现，可选 redis/hazelcast，默认redis
    map: redis                          # 分布式Map实现，可选 redis/hazelcast，默认redis
    mq: redis                           # MQ实现，可选 redis/hazelcast/rabbit，默认redis
    election: redis                     # 领导者选举实现，可选 redis，默认redis
    config:                             # 集群相关配置
      election-period-sec: 60           # 领导者选举时间区间，默认60秒
      ha-enabled: false                 # 是否启用HA，默认为false
  notifies:                             # 通知功能
    "":                                 # 通知的标识
      type: DD                          # 通知的类型，DD=钉钉 MAIL=邮件，邮件方式需要有配置spring.mail下相关的smtp信息 HTTP=自定义HTTP Hook
      defaultReceivers:                 # 默认接收人列表，钉钉为手机号，邮件为邮箱
      dndTimeReceivers:                 # 免扰时间内的接收人列表，只有该列表中的接收人才能在免扰时间内接收通知
      args:                             # 不同类型的参数，邮件不需要设置
        url:                            # type=DD表示钉钉的推送地址
                                        # 说明详见：https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq
                                        # type=HTTP表示HTTP Hook的地址
        msgType:                        # 仅用于type=DD，支持 text/markdown
      strategy:                         # 通知策略
        minIntervalSec: 0               # 最小间隔的通知时间，0表示不设置，如为10则表示10s内只会发送一次
        dndTime:                        # 免扰时间，HH:mm-HH:mm 如，18:00-06:00
                                        # HH:mm-HH:mm，如果两个时间相等表示全天免扰，如果后者大于前者表示跨天免扰
        forceSendTimes: 3               # 同一免扰周期间通知调用达到几次后强制发送
  security:                             # 安全功能
    cors:                               # 跨域设置
      allow-origin: *                   # 允许的来源，建议修改
      allow-methods: POST,GET,OPTIONS,PUT,DELETE,HEAD
                                        # 允许的方法
      allow-headers: x-requested-with,content-type
                                        # 允许的头信息
    token-flag: X-Dew-Token             # Token 标识
    token-kind-flag: X-Dew-Token-Kind   # Token类型 标识
    token-in-header: true               # true：token标识在 header 中，反之在url参数中
    token-hash: false                   # Token值是否需要hash，用于解决token值有特殊字符的情况
    router:                             # 路由功能
      enabled: false                    # 是否启用
      black-uri:                        # URL黑名单，支持ant风格
        &lt;Http Method&gt;: [&lt;URLs&gt;]         # URL黑名单列表
    token-kinds:                        # Token类型，可为不同的Token类型设置不同的过期时间、保留的版本
      &lt;Kind&gt;:                           # Token类型名称，比如 PC/Android/... ，默认会创建名为 DEFAULT 的类型
        expire-sec: 86400               # Token 过期时间（秒）
        revision-history-limit: 0       # Token 保留的历史版本数量， 0表示不保留历史版本，即有新的登录时会删除此类型下之前所有的Token
  cloud:                                # 需要引入 cloud-starter 模块
    trace-log:                          # 追踪日志配置
      enabled: true                     # 是否启用服务API调用（追踪）日志，默认为true
    error:                              # 错误配置
      enabled: false                    # 启用降级邮件通知，默认为false
      notify-flag: __HYSTRIX__          # 默认的通知标识
      notify-event-types: FAILURE,SHORT_CIRCUITED,TIMEOUT,THREAD_POOL_REJECTED,SEMAPHORE_REJECTED
                                        # 通知的事件类型
      notify-include-keys:              # 需监控的方法key值，与notify-exclude-keys互斥，client类名+#+方法名，for example:  ExampleClient#deleteExe(int,String)
      notify-exclude-keys:              # 不需要监控的方法key值，与notify-include-keys互斥，client类名+#+方法名，for example:  ExampleClient#deleteExe(int,String)
  idempotent:                           # 需要引入 idempotent-starter 模块
    default-expire-ms: 3600000          # 设置默认过期时间，1小时
    default-strategy: item              # 设置默认策略，目前支持item(逐条记录)
    default-opt-id-flag: __IDEMPOTENT_OPT_ID__
                                        # 指定幂等操作ID标识，可以位于HTTP Header或请求参数中
  component:                            # 各组件配置
    auth:                               # 需要引入 auth 组件
      token-expire-sec: 2592000         # token有效期，默认为30天
      max-login-error-times: 3          # 最大登录错误次数，默认3次
      clean-error-time-min: 10          # 登录错误限制登录时间，默认为10分钟
      password-salt:                    # 密码加盐
      email-account:                    # 发通知邮件的邮箱账号，比如用来发送用户注册成功邮件
      sdk:                              # SDK配置
          server-url:                   # 服务器地址
          white-list:                   # 白名单，逗号分隔
  mw:                                   # 中间件
    mqtt:                               # MQTT集群实现
      broker:                           # Broker地址，e.g. tcp://127.0.0.1:1883
      clientId: Dew_Cluster_&lt;Cluster.instanceId&gt;
                                        # 连接客户端ID，注意一个集群内客户端ID必须唯一
      persistence:                      # 存储，默认为File，可选 memory
      userName:                         # 用户名
      password:                         # 密码
      timeoutSec:                       # 连接超时时间
      keepAliveIntervalSec:             # 心跳间隔时间
      cleanSession: true                # 是否消除Session

spring:                                 # 常用 Spring 配置
  application:
    name:                               # 项目名称,若使用Dew，请配置
  cloud:
    config:                             # 统一配置
      enabled: true                     # 是否启用
      uri:                              # URL
      username:                         # 认证用户名
      password:                         # 认证密码
    kubernetes:                         # kubernetes相关配置
      ribbon:                           # 客户端轮询配置
        enabled: true                   # 是否启用
    mail:                               # Mail配置，需要hystrix降级通知需加以下配置
      host: smtp.163.com
      username:
      password:
      properties:
        mail:
          smtp:
            auth: true
              starttls:
                enable: true
                required: true
    redis:
      host:                           # Redis主机
      port:                           # Redis端口
      database:                       # Redis数据库
      password:                       # Redis密码
      lettuce:
        pool:                         # 连接池配置
      multi:                          # 多实例支持（Dew功能）
        &lt;key&gt;:                        # 实例名称
                                      # 可用 Dew.cluster.caches.instance(&lt;key&gt;) 获取
                                      # 同时可以用 @Autowired &lt;Key&gt;RedisTemplate 获取Bean
          host:                       # Redis主机
          port:                       # Redis端口
          ...
    rabbitmq:
      host:                           # Rabbit主机
      port:                           # Rabbit端口
      username:                       # Rabbit用户名
      password:                       # Rabbit密码
      virtual-host:                   # Rabbit VH
    hazelcast:
      username:
      password:
      addresses: ["127.0.0.1"]

server:
  port: 8081                          # 服务端口

&lt;application name&gt;:                   # 自定义服务列表，使用此功能需要设置 spring.cloud.kubernetes.ribbon.enabled = false
  ribbon:
    listOfServers: &lt;host&gt;:&lt;port&gt;

management:
  endpoints:
    web:
      base-path: /management          # 管理路径前缀

logging:
  level:
    ROOT: INFO
    ms.dew: DEBUG                     # Dew目录日志配置
    org.springframework.jdbc.core: TRACE
                                      # Jdbc目录日志配置</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="code_spring_boot_code_核心参数"><a class="anchor" href="#code_spring_boot_code_核心参数"></a>3.3.2. <code>Spring boot</code> 核心参数</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html" class="bare">https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html</a>
</td>
</tr>
</table>
</div>
</div>
<div class="sect3">
<h4 id="code_spring_cloud_code_核心参数"><a class="anchor" href="#code_spring_cloud_code_核心参数"></a>3.3.3. <code>Spring cloud</code> 核心参数</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://cloud.spring.io/spring-cloud-static/spring-cloud-kubernetes/2.1.0.RC1/single/spring-cloud-kubernetes.html" class="bare">https://cloud.spring.io/spring-cloud-static/spring-cloud-kubernetes/2.1.0.RC1/single/spring-cloud-kubernetes.html</a>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect2">
<h3 id="framework-best-practices"><a class="anchor" href="#framework-best-practices"></a>3.4. 框架最佳实践</h3>
<div class="sect3">
<h4 id="项目模块设计"><a class="anchor" href="#项目模块设计"></a>3.4.1. 项目模块设计</h4>
<div class="paragraph">
<p><code>Dew</code>推荐的项目结构为：</p>
</div>
<div class="listingblock">
<div class="content">
<pre>X Build Project                 // 构建工程
|- sources                      // 源码目录
|-  |- basics                   // 基础源码，为各服务工程的依赖，要求deploy到Maven仓库
|-  |-  |- parent               // 父工程，所有服务端应用的根POM
|-  |-  |- common               // 公共工程，所有服务端应用的基础依赖
|-  |-  |- common-service       // 公共服务工程，所有服务工程的根POM，自身依赖于common
|-  |-  |- &lt;...&gt;                // 其它基础工程，比如common-spark，大数据服务的基础依赖
|-  |- services                 // 服务源码，对应于一个个微服务
|-  |-  |- &lt;service 1&gt;          // 服务1工程
|-  |-  |- &lt;service ...&gt;        // 其它服务工程
|-  |- sdk                      // SDK源码，如项目需要提供SDK时建立此目录
|-  |-  |- &lt;java&gt;               // Java版本的SDK工程
|-  |-  |- &lt;js&gt;                 // JS版本的SDK工程
|-  |-  |- &lt;rest&gt;               // REST版本的SDK工程
|-  |-  |- &lt;...&gt;                // 其它语言版本的SDK工程
|-  |- terminals                // 终端源码
|-  |-  |- &lt;android&gt;            // Android APP工程
|-  |-  |- &lt;ios&gt;                // IOS APP工程
|-  |-  |- &lt;wechat&gt;             // Wechat工程
|-  |-  |- &lt;...&gt;                // 其它终端工程
|- docs                         // 文档工程，所有团队共同维护，Asciidoc方案，后文会介绍
|-  |- src
|-  |-  |- main
|-  |-  |-  |- asciidoc
|-  |-  |-  |-  |- book.adoc    // 文档主目录
|-  |-  |-  |-  |- &lt;...&gt;        // 分模块建立不同的文档文件
|-  |-  |- resources            // 文档引用资源目录，如图片、CSS
|-  |-  |-  |- images
|-  |-  |-  |-  |- &lt;...&gt;
|-  |-  |- pom.xml              // 文档工程使用Maven管理
|-  |-  |- .gitignore
|- env                          // 环境配置工程，以Spring Cloud Config为例，配置存放于Git
|-  |- application.yml
|-  |- &lt;...&gt;
|-  |- .gitignore
|- pom.xml                      // 构建工程的POM
|- .gitmodules                  // Git子模块定义，所有工程都注册到此文件中
|- .gitignore
|- README.adoc                  // 构建工程使用说明</pre>
</div>
</div>
<div class="paragraph">
<p>推荐各工程使用独立Git库，详见： <a href="https://gudaoxuri.gitbook.io/microservices-architecture/wei-fu-wu-hua-zhi-kai-fa-yu-ce-shi/code-managerment">微服务架构设计-代码管理</a></p>
</div>
</div>
<div class="sect3">
<h4 id="代码包设计"><a class="anchor" href="#代码包设计"></a>3.4.2. 代码包设计</h4>
<div class="paragraph">
<p><code>Dew</code>推荐的包结构为：</p>
</div>
<div class="listingblock">
<div class="content">
<pre>|- X service
|-  |- src
|-  |-  |- main
|-  |-  |-  |- java
|-  |-  |-  |-  |- src
|-  |-  |-  |-  |-  |- x.y.z
|-  |-  |-  |-  |-  |-  |- XApplication <i class="conum" data-value="1"></i><b>(1)</b>
|-  |-  |-  |-  |-  |-  |- XConfig <i class="conum" data-value="2"></i><b>(2)</b>
|-  |-  |-  |-  |-  |-  |- XInitiator <i class="conum" data-value="3"></i><b>(3)</b>
|-  |-  |-  |-  |-  |-  |- controller
|-  |-  |-  |-  |-  |-  |-  |- EventController <i class="conum" data-value="4"></i><b>(4)</b></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>XApplication : 服务启动类</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td>XConfig：当前服务的配置文件（Spring Cloud格式）</td>
</tr>
<tr>
<td><i class="conum" data-value="3"></i><b>3</b></td>
<td>XInitiator：当前服务启动初始化器，原则上所有与数据库、缓存、定时任务相关的初始化操作都应该从此类发起，以方便排错</td>
</tr>
<tr>
<td><i class="conum" data-value="4"></i><b>4</b></td>
<td>EventController：事件处理器，名称可以更具象，原则上所有与MQ receive相关的操作都被视为交互接口，应该放到controller包下
<div class="literalblock">
<div class="content">
<pre>.示例
----
@PostConstruct
public void processTodoAddEvent() {
    // 使用Dew的集群MQ功能实现消息点对点接收
    Dew.cluster.mq.response(Constants.MQ_NOTIFY_TODO_ADD, todo -&gt; {
        logger.info("Received add todo event :" + todo);
    });
}
----</pre>
</div>
</div></td>
</tr>
</table>
</div>
</div>
<div class="sect3">
<h4 id="开发期服务调用"><a class="anchor" href="#开发期服务调用"></a>3.4.3. 开发期服务调用</h4>
<div class="paragraph">
<p>在 <code>Spring Cloud</code> 体系下，服务间调用需要在Kubernetes环境下，这对开发阶段并不友好，可使用如下方案，模拟多个服务间调用。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">spring:
  cloud:
    kubernetes:
      ribbon:
        enabled: false # 禁用服务自动发现

&lt;application name&gt;: # 自定义服务列表，使用此功能需要设置 spring.cloud.kubernetes.ribbon.enabled = false
  ribbon:
    listOfServers: &lt;host&gt;:&lt;port&gt;

# e.g.

tracing-invoke2-example:
  ribbon:
    listOfServers: localhost:8082</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="代码质量管理"><a class="anchor" href="#代码质量管理"></a>3.4.4. 代码质量管理</h4>
<div class="paragraph">
<p>推荐使用 <code>checkstyle</code> 管理代码风格，使用方式：</p>
</div>
<div class="listingblock">
<div class="title">checkstyle使用</div>
<div class="content">
<pre># 在Maven中配置自定义的 checkstyle 文件的路径
&lt;properties&gt;
    &lt;checkstyle.config.path&gt;../../checkstyle/checkstyle.xml&lt;/checkstyle.config.path&gt;
&lt;/properties&gt;

# 使用Maven命令
mvn -P qa compile

# 也可以IDE的checkstyle插件实现</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="code_validated_code_注解"><a class="anchor" href="#code_validated_code_注解"></a>3.4.5. <code>@Validated</code> 注解</h4>
<div class="ulist">
<ul>
<li>
<p>在Spring Controller类里，<code>@Validated</code> 注解初使用会比较不易上手，在此做下总结</p>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>对于基本数据类型和String类型，要使校验的注解生效，需在该类上方加 <code>@Validated</code> 注解</p>
</li>
<li>
<p>对于抽象数据类型，需在形式参数前加<code>@Validated</code>注解</p>
</li>
</ol>
</div>
</li>
</ul>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
Spring对抽象数据类型校验抛出异常为<code>MethodArgumentNotValidException</code>，http状态码为400，对基本数据类型校验抛出异常为<code>ConstraintViolationException</code>，http状态码为500，dew对这两种异常做了统一处理，http状态码均返回200，code为400
</td>
</tr>
</table>
</div>
</div>
<div class="sect3">
<h4 id="code_jackson_code_对于_code_java8_code_时间转换_code_springmvc_code_以_code_jackson_code_接收_code_json_code_数据"><a class="anchor" href="#code_jackson_code_对于_code_java8_code_时间转换_code_springmvc_code_以_code_jackson_code_接收_code_json_code_数据"></a>3.4.6. <code>jackson</code> 对于 <code>Java8</code> 时间转换（ <code>SpringMVC</code> 以 <code>jackson</code> 接收 <code>json</code> 数据）</h4>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>对于 <code>LocalDateTime</code> 类型，需在参数上加 <code>@JsonFormat</code> 注解，如下：<code>@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")</code></p>
</li>
<li>
<p><code>LocalDate,LocalTime,Instant</code> 等，无需配置可自行转换</p>
</li>
</ol>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<code>jackson</code> 对于 <code>LocalDateTime</code> 类型的支持与其他三种类型不具有一致性，这是 <code>jackson</code> 需要优化的一个点
</td>
</tr>
</table>
</div>
</div>
<div class="sect3">
<h4 id="缓存处理"><a class="anchor" href="#缓存处理"></a>3.4.7. 缓存处理</h4>
<div class="paragraph">
<p><code>Spring Cache</code> 提供了很好的注解式缓存，但默认没有超时，需要根据使用的缓存容器特殊配置</p>
</div>
<div class="listingblock">
<div class="title">Redis缓存过期时间设置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">@Bean
RedisCacheManager cacheManager() {
    final RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
    redisCacheManager.setUsePrefix(true);
    redisCacheManager.setDefaultExpiration(&lt;过期秒数&gt;);
    return redisCacheManager;
}</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="jdbc_批量插入性能问题"><a class="anchor" href="#jdbc_批量插入性能问题"></a>3.4.8. jdbc 批量插入性能问题</h4>
<div class="paragraph">
<p>如果不开启rewriteBatchedStatements=true，那么jdbc会把批量插入当做一行行的单条处理，也就没有达到批量插入的效果</p>
</div>
<div class="listingblock">
<div class="title">jdbc配置示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/dew?useUnicode=true&amp;characterEncoding=utf-8&amp;rewriteBatchedStatements=true
    username: root
    password: 123456</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="部署运维_devops_chapter"><a class="anchor" href="#部署运维_devops_chapter"></a>4. 部署运维 DevOps Chapter</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="devops-quick-start"><a class="anchor" href="#devops-quick-start"></a>4.1. DevOps快速入门</h3>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
本文以 To-Do 项目为示例讲解 <code>Dew</code> DevOps部分的入门操作，项目地址： <a href="https://github.com/dew-ms/devops-example-todo" class="bare">https://github.com/dew-ms/devops-example-todo</a> 。
</td>
</tr>
</table>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
关于 To-Do 项目的介绍请先阅读 <a href="#framework-quick-start">框架快速入门</a>
</td>
</tr>
</table>
</div>
<div class="sect3">
<h4 id="功能体验_2"><a class="anchor" href="#功能体验_2"></a>4.1.1. 功能体验</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
体验环境要求安装 Java(&gt;=8)、Maven、NodeJS(&gt;=8)
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>目录结构如下：</p>
</div>
<div class="listingblock">
<div class="content">
<pre># ======= 前置准备 =======
# 执行dew-devops.sh根据提示创建项目
sh dew-devops.sh
# 添加本地Host映射
-
x.x.x.x todo-uat.dew.ms
x.x.x.x todo-api-uat.dew.ms
-
# ======= 前置准备 =======
git clone https://github.com/dew-ms/devops-example-todo.git
cd devops-example-todo
# 执行安装
mvn install -Dmaven.test.skip=true
# 部署到Kubernetes
mvn -P devops deploy -Ddew_devops_profile=uat

# 打开浏览器，输入 http://todo-uat.dew.ms，切换到移动模式体验</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="发生了什么"><a class="anchor" href="#发生了什么"></a>4.1.2. 发生了什么？</h4>
<div class="paragraph">
<p>当执行 <code>sh dew-devops.sh</code> 创建项目时实际上做了：</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>创建一个Harbor project及对应的用户，并做了权限绑定</p>
</li>
<li>
<p>创建一个kubernetes namespace</p>
</li>
<li>
<p>为这个kubernetes namespace绑定service-discovery-client角色，用于服务间发现</p>
</li>
<li>
<p>为这个kubernetes namespace创建docker-registry Secret，用于Harbor认证</p>
</li>
<li>
<p>为这个Kubernetes namespace创建Ingress，绑定服务与域名，内容类似：</p>
<div class="literalblock">
<div class="content">
<pre>apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
  name: dew-ingress
  namespace: dew-uat
spec:
  rules:
  - host: todo-api-uat.dew.ms
    http:
      paths:
      - backend:
          serviceName: todo-kernel
          servicePort: 8080
        path: /
  - host: todo-uat.dew.ms
    http:
      paths:
      - backend:
          serviceName: todo-frontend
          servicePort: 80
        path: /</pre>
</div>
</div>
</li>
</ol>
</div>
<div class="paragraph">
<p>当执行 <code>mvn -P devops dew:release -Ddew_devops_profile=uat</code> 时实际上做了：</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>根据根目录的 <code>pom.xml</code> 配置找到所有的Maven模块并按依赖排序</p>
</li>
<li>
<p>对比当前的Git版本(git commit)与已部署的服务版本找到之间变更的文件</p>
</li>
<li>
<p>遍历每个Maven模块找到当前有文件变更的模块，视这些模块为要部署的模块</p>
</li>
<li>
<p>提示用户是否部署</p>
</li>
<li>
<p>根据模块对应的类型执行不同的准备操作：</p>
<div class="olist loweralpha">
<ol class="loweralpha" type="a">
<li>
<p>JVM服务(Spring Boot): 执行 <code>mvn package spring-boot-maven-plugin:repackage</code> 打出fatjar</p>
</li>
<li>
<p>前端工程: 执行<code>cd ./frontend &amp;&amp; npm install &amp;&amp; npm install -g @tarojs/cli &amp;&amp; set NODE_ENV=test &amp;&amp; npm run build:h5</code> 打出dist文件</p>
</li>
</ol>
</div>
</li>
<li>
<p>根据模块对应的类型执行不同的编译操作：</p>
<div class="olist loweralpha">
<ol class="loweralpha" type="a">
<li>
<p>JVM服务/前端工程: 执行 <code>docker build</code> 与 <code>docker push</code> 发布到 Harbor</p>
</li>
</ol>
</div>
</li>
<li>
<p>根据模块对应的类型执行不同的部署操作：</p>
<div class="olist loweralpha">
<ol class="loweralpha" type="a">
<li>
<p>JVM类库/Pom工程: 执行 <code>mvn deploy</code> 发布到Maven仓库，并创建 configmap 打上版本记录</p>
</li>
<li>
<p>JVM服务/前端工程: 部署 deployment 与 service 到 kuberenetes，并创建 configmap 打上版本记录</p>
</li>
</ol>
</div>
</li>
</ol>
</div>
</div>
</div>
<div class="sect2">
<h3 id="devops-user-manual"><a class="anchor" href="#devops-user-manual"></a>4.2. DevOps使用手册</h3>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<code>Dew</code> DevOps部分的核心是由 <code>dew-maven-plugin</code> 实现。
</td>
</tr>
</table>
</div>
<div class="sect3">
<h4 id="devops-user-manual-import"><a class="anchor" href="#devops-user-manual-import"></a>4.2.1. 引入方式</h4>
<div class="sect4">
<h5 id="使用_code_dew_framework_code"><a class="anchor" href="#使用_code_dew_framework_code"></a>使用 <code>Dew Framework</code></h5>
<div class="paragraph">
<p>如果您的项目使用 <code>Dew Framework</code> 构建则只要确保引入了 <code>parent-starter</code> 即可。</p>
</div>
</div>
<div class="sect4">
<h5 id="不使用_code_dew_framework_code"><a class="anchor" href="#不使用_code_dew_framework_code"></a>不使用 <code>Dew Framework</code></h5>
<div class="paragraph">
<p><code>Dew DevOps</code> 完全可以独立于 <code>Dew Framework</code> 使用，只需要在项目的父POM中加上如下配置即可。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">&lt;profile&gt;
    &lt;id&gt;devops&lt;/id&gt;
    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;ms.dew&lt;/groupId&gt;
                &lt;artifactId&gt;dew-maven-plugin&lt;/artifactId&gt;
                &lt;!--生产环境请选择合适的版本!--&gt;
                &lt;version&gt;${dew.version}&lt;/version&gt;
                &lt;executions&gt;
                    &lt;execution&gt;
                        &lt;goals&gt;
                            &lt;goal&gt;init&lt;/goal&gt;
                            &lt;goal&gt;release&lt;/goal&gt;
                        &lt;/goals&gt;
                    &lt;/execution&gt;
                &lt;/executions&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;
&lt;/profile&gt;</code></pre>
</div>
</div>
</div>
</div>
<div class="sect3">
<h4 id="支持的项目类型"><a class="anchor" href="#支持的项目类型"></a>4.2.2. 支持的项目类型</h4>
<div class="paragraph">
<p>目前支持以下项目类型：</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>Spring Boot/Cloud</code> 发布到 <code>Kubernetes</code></p>
</li>
<li>
<p><code>NodeJS</code> 发布到 <code>Kubernetes</code></p>
</li>
<li>
<p><code>JVM Libraries</code> 发布到 <code>Maven Repository</code></p>
</li>
<li>
<p><code>POM</code> 发布到 <code>Maven Repository</code></p>
</li>
</ul>
</div>
</div>
<div class="sect3">
<h4 id="nodejs项目的特殊处理"><a class="anchor" href="#nodejs项目的特殊处理"></a>4.2.3. NodeJS项目的特殊处理</h4>
<div class="paragraph">
<p>NodeJS项目需要添加Maven项目。</p>
</div>
<div class="listingblock">
<div class="title">处理步骤</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">&lt;!-- 1. 添加pom.xml文件，格式如下 --&gt;
&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;

  &lt;!-- parent要最终指向带有 profile为 devops 的父pom，比如 parent-starter --&gt;
  &lt;parent&gt;
    ...
  &lt;/parent&gt;

  &lt;groupId&gt;...&lt;/groupId&gt;
  &lt;artifactId&gt;...&lt;/artifactId&gt;
  &lt;packaging&gt;pom&lt;/packaging&gt;

  ...
&lt;/project&gt;

&lt;!-- 2. 将此项目做为module引入 --&gt;</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">pom.xml示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!--
  ~ Copyright 2019. the original author or authors.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  --&gt;

&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;

  &lt;parent&gt;
    &lt;groupId&gt;ms.dew&lt;/groupId&gt;
    &lt;artifactId&gt;parent-starter&lt;/artifactId&gt;
    &lt;version&gt;2.0.0-beta2&lt;/version&gt;
  &lt;/parent&gt;

  &lt;groupId&gt;ms.dew.devops.it&lt;/groupId&gt;
  &lt;artifactId&gt;todo-frontend&lt;/artifactId&gt;
  &lt;packaging&gt;pom&lt;/packaging&gt;

  &lt;properties&gt;
    &lt;sonar.skip&gt;true&lt;/sonar.skip&gt;
  &lt;/properties&gt;

  &lt;repositories&gt;
    &lt;repository&gt;
      &lt;id&gt;central&lt;/id&gt;
      &lt;url&gt;https://repo.maven.apache.org/maven2&lt;/url&gt;
    &lt;/repository&gt;
    &lt;repository&gt;
      &lt;id&gt;oss-public&lt;/id&gt;
      &lt;url&gt;https://oss.sonatype.org/content/groups/public&lt;/url&gt;
    &lt;/repository&gt;
    &lt;repository&gt;
      &lt;id&gt;oss-snapshot&lt;/id&gt;
      &lt;url&gt;https://oss.sonatype.org/content/repositories/snapshots&lt;/url&gt;
      &lt;snapshots&gt;
        &lt;enabled&gt;true&lt;/enabled&gt;
        &lt;checksumPolicy&gt;warn&lt;/checksumPolicy&gt;
      &lt;/snapshots&gt;
    &lt;/repository&gt;
  &lt;/repositories&gt;

&lt;/project&gt;</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="项目配置"><a class="anchor" href="#项目配置"></a>4.2.4. 项目配置</h4>
<div class="listingblock">
<div class="title">项目初始化</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">curl -O https://raw.githubusercontent.com/gudaoxuri/dew/master/devops/sh/dew-devops.sh
sh dew-devops.sh
# 选择对应操作，根据提示操作即可
# 选择 1 进行集群初始化
# 选择 2 安装 Gitlab runner
# 选择 3 创建项目， 创建项目，为项目创建namespace、ingress、对应 Harbor 账户和镜像仓库等</code></pre>
</div>
</div>
<div class="paragraph">
<div class="title">Dew和Maven的配置</div>
<p>见：<a href="#devops-configuration">DevOps配置速查</a></p>
</div>
</div>
<div class="sect3">
<h4 id="打包部署"><a class="anchor" href="#打包部署"></a>4.2.5. 打包部署</h4>
<div class="paragraph">
<p>用于部署一个项目下的多个应用，此命令多在项目根目录下执行。</p>
</div>
<div style='position:relative; padding-bottom:calc(64.51% + 44px)'><iframe src='https://gfycat.com/ifr/WellwornAmbitiousBalloonfish' frameborder='0' scrolling='no' width='100%' height='100%' style='position:absolute;top:0;left:0;' allowfullscreen></iframe></div>
<div class="listingblock">
<div class="title">命令</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 基础命令
mvn deploy

# ====================== 附加参数 ======================
# dew_devops_profile                           # 指定的环境
# dew_devops_kube_config                       # Kubernetes Base64 后的配置，使用 ``echo $(cat ~/.kube/config | base64) | tr -d " "`` 获取
# dew_devops_docker_host                       # Dockerd的Host e.g. tcp://dockerd.dew.ms:2375
# dew_devops_docker_registry_url               # Docker Registry Url  e.g. https://harbor.dew.ms/v2
# dew_devops_docker_registry_username          # Docker Registry 用户名
# dew_devops_docker_registry_password          # Docker Registry 密码
# dew_devops_assignation_projects              # 指定部署应用artifactId  多个以逗号分隔
# dew_devops_quiet                             # 是否静默处理</code></pre>
</div>
</div>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
在CI/CD工具中附加参数的kubernetes、docker相关参数应作为变量配置到执行器中，对应用本身不可见。
</td>
</tr>
</table>
</div>
<div class="ulist">
<div class="title">核心流程</div>
<ul>
<li>
<p>依赖排序，使用Maven自身的Reactor机制为项目中的各应用（模块）做依赖排序</p>
</li>
<li>
<p>配置装配，根据 <a href="#devops-configuration-dew-inheritance-rules">继承规则</a> 完成配置组装</p>
</li>
<li>
<p>部署探测，用于确定要部署哪些应用</p>
<div class="ulist">
<ul>
<li>
<p>命令优先，如指定部署应用( <code>dew_devops_assignation_projects</code> ) 则短路后续探测</p>
</li>
<li>
<p>配置次之，如配置中指定了 <code>skip: true</code> 则视之为不需要部署的应用</p>
</li>
<li>
<p>版本比对，比对已部署的各应用版本与当前各应用的代码，找到有代码变更的应用，视之为要部署的应用</p>
</li>
<li>
<p>依赖传递分析，分析没有代码变更的应用，如其依赖包有变更（比如依赖了某些SNAPSHOT的模块）时这些应用也被视为要部署的应用</p>
</li>
</ul>
</div>
</li>
<li>
<p>编译准备，根据配置装配时分析到的应用类型，为不同应用类型执行不同的编译方案</p>
<div class="ulist">
<ul>
<li>
<p><code>Spring Boot/Cloud</code> 应用需要执行 <code>repackage</code> 以打出 <code>fatjar</code></p>
</li>
<li>
<p><code>NodeJS</code> 应用需要执行对应的 <code>node</code> 命令完成打包</p>
</li>
</ul>
</div>
</li>
<li>
<p>单元测试（可通过 <code>-Dmaven.test.skip=true</code> 跳过）</p>
</li>
<li>
<p>发布部署，根据配置装配时分析到的部署类型，为不同部署类型执行不同的发布方案</p>
<div class="ulist">
<ul>
<li>
<p><code>Maven</code> 方式直接使用 <code>mvn deploy</code></p>
</li>
<li>
<p><code>Kubernetes</code> 方式先编译 <code>Docker Image</code> 并上传到 <code>Registry</code> ，再部署到 <code>Kubernetes</code></p>
</li>
</ul>
</div>
</li>
<li>
<p>版本处理，部署完成后打上当前版本号</p>
</li>
<li>
<p>结果通知，发送钉钉或HTTP请求完成结果通知</p>
</li>
</ul>
</div>
<div class="paragraph">
<div class="title">版本重用模式</div>
<p>一般项目都会要求生产环境的代码要与预发/UAT环境最后一次验收通过的代码保持一致，具体到容器中即要保证两者的 <code>Docker Image</code> 相同。</p>
</div>
<div class="paragraph">
<p>版本重用模式可以实现部署时重用某一环境 <code>Docker Image</code> 的要求。其核心流程如下：</p>
</div>
<div class="ulist">
<ul>
<li>
<p>重用判定，以一套规则判断当前是否在重用模式下，也可使用 <code>disableReuseVersion: true/false , reuseLastVersionFromProfile: &lt;目标环境名称&gt;</code> 指定是否是重用模式</p>
</li>
<li>
<p>镜像获取，从目标环境获取镜像并上传到当前环境的 <code>Registry</code> 中</p>
</li>
<li>
<p>后续操作同普通模式的流程</p>
</li>
</ul>
</div>
<div class="paragraph">
<div class="title">并行部署</div>
<p><code>Dew</code> 支持使用Maven的 <code>-T </code> 实现并行编译部署，例如： <code>mvn -T 1C deploy</code></p>
</div>
<div id="devops-user-manual-release-skip" class="paragraph">
<div class="title">不需要部署的应用处理</div>
<p><code>Dew</code> 会自行判断应用是否需要部署，对于不需要部署的应用为加速执行会设置一系列的skip，
部署执行的是 <a href="https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html">Default Lifecycle</a> ，
默认包含了如下插件的skip：</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>Core plugins（clean、compiler、deploy、failsafe、install、resources、site、surefire）</p>
</li>
<li>
<p>Packaging types/tools（war、jar、source）</p>
</li>
<li>
<p>Reporting plugins（checkstyle）</p>
</li>
<li>
<p>Tools（assembly、dependency、gpg）</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>同时 <code>Dew</code> 提供了 <code>dew.devops.skip</code> ，用户可自行添加到其它插件、环境中。</p>
</div>
<div class="listingblock">
<div class="title">dew.devops.skip使用示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-xml" data-lang="xml">&lt;!-- 动态跳过特定插件 --&gt;
&lt;plugin&gt;
     &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
     &lt;artifactId&gt;maven-antrun-plugin&lt;/artifactId&gt;
     &lt;configuration&gt;
         &lt;skip&gt;${dew.devops.skip}&lt;/skip&gt;
     &lt;/configuration&gt;
&lt;/plugin&gt;

&lt;!--
  并不是所有插件都提供了skip配置（比如maven-shade-plugin），这时可以使用profile + activation的方式解决

  1. 定义一个profile，id随意
  2. activation &gt; property 中添加 name 为 dew.devops.skip , value 为 !true 的属性
  3. 添加需要动态skip的内容，可以是插件、依赖、属性等
--&gt;
&lt;profile&gt;
    &lt;id&gt;devops-ext&lt;/id&gt;
    &lt;activation&gt;
        &lt;property&gt;
            &lt;name&gt;dew.devops.skip&lt;/name&gt;
            &lt;value&gt;!true&lt;/value&gt;
        &lt;/property&gt;
    &lt;/activation&gt;
    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                &lt;artifactId&gt;maven-shade-plugin&lt;/artifactId&gt;
                ...
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;
&lt;/profile&gt;</code></pre>
</div>
</div>
<div class="paragraph">
<div class="title">使用 <code>javaagent</code> 动态跳过不需要部署的应用</div>
<p>详见 <a href="#dew-devops-maven-agent">Dew DevOps Maven Agent</a></p>
</div>
</div>
<div class="sect3">
<h4 id="版本回滚"><a class="anchor" href="#版本回滚"></a>4.2.6. 版本回滚</h4>
<div class="paragraph">
<p>用于回滚某一个应用，此命令在要回滚的应用目录下执行。</p>
</div>
<div class="listingblock">
<div class="title">命令</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 基础命令
mvn dew:rollback

# ====================== 附加参数 ======================
# dew_devops_profile                           # 指定的环境
# dew_devops_kube_config                       # Kubernetes Base64 后的配置，使用 ``echo $(cat ~/.kube/config | base64) | tr -d " "`` 获取
# dew_devops_docker_host                       # Dockerd的Host e.g. tcp://dockerd.dew.ms:2375
# dew_devops_docker_registry_url               # Docker Registry Url  e.g. https://harbor.dew.ms/v2
# dew_devops_docker_registry_username          # Docker Registry 用户名
# dew_devops_docker_registry_password          # Docker Registry 密码</code></pre>
</div>
</div>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
在CI/CD工具中附加参数的kubernetes、docker相关参数应作为变量配置到执行器中，对应用本身不可见。
</td>
</tr>
</table>
</div>
</div>
<div class="sect3">
<h4 id="应用卸载"><a class="anchor" href="#应用卸载"></a>4.2.7. 应用卸载</h4>
<div class="paragraph">
<p>用于卸载某一个应用，但不会删除其对应的版本信息，此命令在要卸载的应用目录下执行。</p>
</div>
<div class="listingblock">
<div class="title">命令</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 基础命令
mvn dew:unrelease

# ====================== 附加参数 ======================
# dew_devops_profile                           # 指定的环境
# dew_devops_kube_config                       # Kubernetes Base64 后的配置，使用 ``echo $(cat ~/.kube/config | base64) | tr -d " "`` 获取</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="日志查看"><a class="anchor" href="#日志查看"></a>4.2.8. 日志查看</h4>
<div class="paragraph">
<p>用于查看某一个应用的日志，此命令在要查看日志的应用目录下执行。</p>
</div>
<div class="listingblock">
<div class="title">命令</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 基础命令
mvn dew:log

# ====================== 附加参数 ======================
# dew_devops_profile                           # 指定的环境
# dew_devops_kube_config                       # Kubernetes Base64 后的配置，使用 ``echo $(cat ~/.kube/config | base64) | tr -d " "`` 获取
# dew_devops_podName                           # 要使用的Pod名称，如不填写当存在多个Pod时会要求用户选择
# dew_devops_log_follow                        # 是否滚动查看日志</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="伸缩扩展"><a class="anchor" href="#伸缩扩展"></a>4.2.9. 伸缩扩展</h4>
<div class="paragraph">
<p>用于伸缩某一个应用，此命令在要伸缩的应用目录下执行。</p>
</div>
<div class="listingblock">
<div class="title">命令</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 基础命令
mvn dew:scale

# ====================== 附加参数 ======================
# dew_devops_scale_replicas                    # 伸缩Pod数量
# dew_devops_scale_auto                        # 是否启用自动伸缩
# dew_devops_scale_auto_minReplicas            # 自动伸缩Pod数下限
# dew_devops_scale_auto_maxReplicas            # 自动伸缩Pod数上限
# dew_devops_scale_auto_cpu_averageUtilization # 自动伸缩条件：CPU平均使用率标识</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<code>dew_devops_scale_auto: false</code>（默认情况） 时 <code>dew_devops_scale_replicas</code> 为必要参数，反之另几个参数为必要参数。
</td>
</tr>
</table>
</div>
</div>
<div class="sect3">
<h4 id="应用刷新"><a class="anchor" href="#应用刷新"></a>4.2.10. 应用刷新</h4>
<div class="paragraph">
<p>用于刷新重启某一个应用，此命令在要重启的应用目录下执行。</p>
</div>
<div class="listingblock">
<div class="title">命令</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 基础命令
mvn dew:refresh</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="ci_cd工具支持_gitlab_ci"><a class="anchor" href="#ci_cd工具支持_gitlab_ci"></a>4.2.11. CI/CD工具支持-Gitlab CI</h4>
<div class="paragraph">
<p>应用的部署与回滚为 <code>DevOps</code> 的核心操作，这些操作应该与 <code>CI/CD</code> 工具集成使用。</p>
</div>
<div class="paragraph">
<p><code>Dew</code> 官方支持与 <code>Gitlab CI</code> 集成，步骤如下：</p>
</div>
<div class="ulist">
<ul>
<li>
<p>使用 <code>dew-devops.sh</code> 初始化项目时填写 <code>Gitlab CI</code> 相关信息</p>
</li>
<li>
<p>在项目根目录下添加 <code>.gitlab-ci.yml</code> 即可，格式如下：</p>
</li>
</ul>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml">stages:
  - deploy

cache:
  paths:
    - frontend/node_modules/
    - .m2/

uat deploy:
  stage: deploy
  only:
    - uat
  tags:
    - uat
  script:
    - mvn -P devops dew:release

prod deploy:
  stage: deploy
  only:
    - prod
  tags:
    - prod
  script:
    - mvn -P devops dew:release</code></pre>
</div>
</div>
<div class="paragraph">
<div class="title">自动化部署</div>
<p>当对应的分支有commit时即可触发部署。</p>
</div>
<div class="paragraph">
<div class="title">版本回滚</div>
<p>在 <code>Gitlab CI Pipelines</code> 中进入要回滚的 <code>Pipeline</code> ，点击 <code>Retry</code> 即可。</p>
</div>
<div class="imageblock">
<div class="content">
<img src="./images/gitlab-ci-rollback.png" alt="gitlab ci rollback">
</div>
</div>
</div>
<div class="sect3">
<h4 id="执行通知"><a class="anchor" href="#执行通知"></a>4.2.12. 执行通知</h4>
<div class="paragraph">
<p>目前支持 <code>钉钉</code> 与 <code>HTTP</code> 两种方式：</p>
</div>
<div class="ulist">
<ul>
<li>
<p>钉钉支持markdown格式的钉钉消息</p>
</li>
<li>
<p>HTTP数据结构分为以下几种情况</p>
</li>
</ul>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-js" data-lang="js">// PROCESS_EMPTY(空项目)
{
	"title": "DevOps process successful",         // 消息标题
	"content": {                                  // 消息内容
		"kind": "PROCESS_EMPTY",                  // 过程处理类型
		"ci": "",                                 // CI操作
		"message": {}                             // 消息结果内容
	}
}
// PROCESS_INIT(初始化)
{
	"title": "DevOps process successful",         // 消息标题
	"content": {                                  // 消息内容
		"kind": "PROCESS_INIT",                   // 过程处理类型
		"ci": "",                                 // CI操作
		"message": {                              // 消息结果内容
			"profile": "uat",                     // 分支或者环境
			"project": "todo-parent",             // 应用名称
		}
	}
}
// PROCESS_EXECUTE_SUCCESS(成功)/PROCESS_EXECUTE_FAILURE(失败)
{
	"title": "DevOps process successful",         // 消息标题
	"content": {                                  // 消息内容
		"kind": "PROCESS_EXECUTE_SUCCESS",        // 过程处理类型
		"ci": "",                                 // CI操作
		"message": {                              // 消息结果内容
			"mojoName": "release",                // mvn操作步骤
            "profile": "uat",                     // 分支或者环境
            "project": "todo-parent",             // 应用名称
            "successfulFlag": "Successful",       // 成功标识
            "group": "ms.dew.devops.it",          // 应用group名称
            "message":"***",                      // 错误信息
            "throwable":"**"                      // 错误
		}
	}
}
// PROCESS_GLOBAL_ERROR(全局错误)
{
	"title": "DevOps process successful",         // 消息标题
	"content": {                                  // 消息内容
		"kind": "PROCESS_GLOBAL_ERROR",           // 过程处理类型
		"ci": "",                                 // CI操作
		"message": {                              // 消息结果内容
			"error": "release",                   // 错误信息
            "errorStackTrace": "uat"              // 错误堆栈信息
		}
	}
}
// PROCESS_SHUTDOWN(关闭)
{
	"title": "DevOps process successful",         // 消息标题
	"content": {                                  // 消息内容
		"kind": "PROCESS_SHUTDOWN",               // 过程处理类型
		"ci": "",                                 // ci操作
		"message": {                              // 消息结果内容
			"successfulExecProjects": [{          // 成功的应用
			    "groupId":"com.terran.message",   // maven groupId
			    "artifactId":"dingtalk",          // maven artifactId
			    "execMojos":"release",            // 执行成功的操作，多个以逗号分隔 有release(发布)、unrelease(卸载)、scale(扩容)、rollback(回滚)四种操作
			    "name":"dingtalk"                 // maven应用名称
			    "reason":null
			}],
			"failureExecProjects": [{             // 失败的应用
                "groupId":"com.terran.message",   // maven groupId
                "artifactId":"dingtalk",          // maven artifactId
                "name":"dingtalk",                // maven应用名称
                "reason":"***"                    // 失败原因
            }],
			"noneExecProjects": [{                // 没有执行的应用，在某一应用处理失败后后续的应用都不会被执行
                "groupId":"com.terran.message",   // maven groupId
                "artifactId":"dingtalk",          // maven artifactId
                "name":"dingtalk"                 // maven应用名称
			}],
			"ignoreExecProjects": [{              // 忽略的应用
                "groupId":"com.terran.message",   // maven groupId
                "artifactId":"dingtalk",          // maven artifactId
                "name":"dingtalk",                // maven应用名称
                "reason":"***"                    // 忽略原因
			}]
		}
	}
}</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="dew-devops-deploy"><a class="anchor" href="#dew-devops-deploy"></a>4.2.13. Dew DevOps 脚本部署</h4>
<div class="paragraph">
<p>此为Dew DevOps 的脚本 <code>dew-devops.sh</code> 部署相关说明。集成说明参见： <a href="http://doc.dew.ms/#%E9%83%A8%E7%BD%B2%E8%BF%90%E7%BB%B4_devops_chapter">Dew Devops 部署运维</a></p>
</div>
<div class="sect4">
<h5 id="before_started"><a class="anchor" href="#before_started"></a>Before Started</h5>
<div class="paragraph">
<p>环境准备：</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>正常运行的Kubernetes集群</p>
</li>
<li>
<p>安装 Helm</p>
</li>
<li>
<p>可用的 Harbor 镜像仓库</p>
</li>
<li>
<p>GitLab 仓库</p>
</li>
<li>
<p>MinIO，并创建好对应的 bucket(用于进行Runner的缓存存储)</p>
</li>
<li>
<p>DockerD（用于Runner中dew-maven-plugin进行项目镜像的打包上传拉取）</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>项目准备：</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>在项目代码中添加并配置 <code>.dew</code> 文件（详见 <a href="http://doc.dew.ms/#devops-configuration-dew">.dew 配置详情</a>）</p>
</li>
<li>
<p>在项目代码中添加并配置 <code>.gitlab-ci.yml</code> 文件（参考 <a href="http://doc.dew.ms/#dew_ci_cd_gitlab_ci_%E5%AE%9E%E7%8E%B0">Gitlab CI 实现</a>）</p>
</li>
</ol>
</div>
</div>
<div class="sect4">
<h5 id="使用方式"><a class="anchor" href="#使用方式"></a>使用方式</h5>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">curl -O https://raw.githubusercontent.com/gudaoxuri/dew/master/devops/sh/dew-devops.sh
sh dew-devops.sh</code></pre>
</div>
</div>
<div class="sect5">
<h6 id="选项介绍"><a class="anchor" href="#选项介绍"></a>选项介绍</h6>
<div class="paragraph">
<div class="title">Init cluster</div>
<p>集群初始化：创建一个用于集群服务发现的 ClusterRole</p>
</div>
<div class="paragraph">
<div class="title">Install Gitlab runner</div>
<p>Gitlab runner 安装，用于配合 <a href="http://doc.dew.ms/#dew_ci_cd_gitlab_ci_%E5%AE%9E%E7%8E%B0">Gitlab CI</a>部署项目。
此选项功能：</p>
</div>
<div class="listingblock">
<div class="content">
<pre>创建一个ConfigMap，用于保存Maven的settings.xml。
创建一个Secret用于保存MinIO的key和secret。若不使用MinIO作为缓存存储，需要自行配置。
安装Gitlab Runner,并生成gitlab-runner/gitlab-runner-helm-installation.sh，以便查看配置。</pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
若不使用MinIO作为缓存存储，请参考 <a href="https://gitlab.com/charts/gitlab-runner/blob/master/values.yaml">Gitlab Runner Chart</a>自行配置。
</td>
</tr>
</table>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>Gitlab Runner 部分配置参数说明：</p>
</div>
</blockquote>
</div>
<table class="tableblock frame-all grid-all spread">
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">配置项</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">说明</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">runners.image</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">runner的默认容器镜像，为了保证能使用最新的Dew DevOps功能，建议使用dewms/devops:latest；或者也可使用基于此镜像的自定义镜像</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">runners.tags</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">用于Gitlab CI 触发部署的runner的tag，此脚本中，tags和项目的profile保持一致。也可在执行脚本时自行修改。</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">runners.env.dew_devops_profile</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">此为指定部署项目的profile，默认与runners.tags只保持一致。可在执行脚本时自行修改。</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">runners.env.dew_devops_XXX</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">更多runner.env配置项，详见： <a href="http://doc.dew.ms/#maven_%E9%85%8D%E7%BD%AE">Maven 配置</a></p></td>
</tr>
</tbody>
</table>
<div class="paragraph">
<div class="title">Create a project</div>
<p>创建项目：用于给需要部署的项目来创建一个部署的命名空间等。
详见： <a href="http://doc.dew.ms/#%E5%8F%91%E7%94%9F%E4%BA%86%E4%BB%80%E4%B9%88">创建项目</a></p>
</div>
</div>
</div>
</div>
<div class="sect3">
<h4 id="监控管理"><a class="anchor" href="#监控管理"></a>4.2.14. 监控管理</h4>

</div>
</div>
<div class="sect2">
<h3 id="devops-env-install"><a class="anchor" href="#devops-env-install"></a>4.3. 测试、生产环境安装配置</h3>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
<div class="paragraph">
<p>本文会给出使用代理与不使用代理的安装、配置方式，但强烈推荐使用代理方式，详见 <a href="#proxies">代理选择与设置</a> 。</p>
</div>
</td>
</tr>
</table>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>本文各服务使用 <code>x.dew.ms</code> 做为访问的域名，可根据实际情况替换。</p>
</div>
</td>
</tr>
</table>
</div>
<div class="sect3">
<h4 id="服务规划"><a class="anchor" href="#服务规划"></a>4.3.1. 服务规划</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
常规的项目研发多会分 <code>开发(dev)、测试(test)、预发(pre-prod)/用户验收(uat)、生产(prod)</code> 等多个环境。
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>服务整体上三类：</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>公共支撑服务，如 Gitlab 、 Harbor 等，这些服务要求所有环境共用、互通</p>
</li>
<li>
<p>每个环境独立部署的支撑服务， 如 RabbitMQ、PostgreSql、Redis、dnsmasq、Minio 等，出于数据、资源隔离的要求这些服务各个环境分别部署</p>
</li>
<li>
<p>每个环境的Docker与Kubernetes集群，原则上各环境使用独立的集群，如果共用集群时需要使用<code>namespace</code>加以区分</p>
</li>
</ol>
</div>
<table class="tableblock frame-all grid-all spread">
<caption class="title">Table 2. 推荐的服务列表</caption>
<colgroup>
<col style="width: 25%;">
<col style="width: 25%;">
<col style="width: 25%;">
<col style="width: 25%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">分类</th>
<th class="tableblock halign-left valign-top">域名/主机名</th>
<th class="tableblock halign-left valign-top">服务</th>
<th class="tableblock halign-left valign-top">备注</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">公共支撑服务</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">domain:gitlab.dew.ms</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Gitlab</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Gitlab及其CI/CD服务</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">公共支撑服务</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">domain:harbor.dew.ms</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Harbor</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Docker私有库服务</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">公共支撑服务</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">domain:maven.dew.ms</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Maven</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Maven私有库服务</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">环境相关的支撑服务</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Dnsmasq</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">轻量级DNS解析服务</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">环境相关的支撑服务</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">domain:minio.dew.ms</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Minio</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">分布式对象存储服务，用于CI/CD时做为分布式缓存</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">环境相关的支撑服务</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">domain:nfs.dew.ms</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">NFS</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">用于Kuernetes persistent volumes的文件存储服务，生产环境建议使用云厂商的分布式文件存储或 CephFS，支持的类型见
                                                                   <a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes" class="bare">https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes</a></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">容器集群</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">hostname:k8s-X</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Docker</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Docker容器服务，部署到所有Kubernetes所在的节点</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">容器集群</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">hostname:k8s-X</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">&lt;CNI&gt;</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Kubernetes CNI服务，部署到所有Kubernetes所在的节点，本文使用 Flannel</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">容器集群</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">hostname:k8s-masterX</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Kubernetes Master</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Kubernetes Master服务，可做HA</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">容器集群</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">hostname:k8s-masterX</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Helm tiller</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Helm服务，部署到Kubernetes Master所在节点</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">容器集群</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">hostname:k8s-nodeX</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Kubernetes Node</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Kubernetes Node服务，至少3个节点</p></td>
</tr>
</tbody>
</table>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
<div class="ulist">
<ul>
<li>
<p>容器集群各节点主机名与IP的映射配置到 /etc/hosts 中</p>
</li>
<li>
<p>kubernetes Node 应至少分两个组: <code>group=app</code> 用于运行应用， <code>group=devops</code> 用于运行运维管理工具。</p>
</li>
</ul>
</div>
</td>
</tr>
</table>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
各支撑服务（中间件）的安装见  <a href="#middleware">常用中间件安装配置</a> ，下文介绍容器服务的安装配置。
</td>
</tr>
</table>
</div>
</div>
<div class="sect3">
<h4 id="基础配置"><a class="anchor" href="#基础配置"></a>4.3.2. 基础配置</h4>
<div class="paragraph">
<p><strong>以 Centos7 为例，各节点做好ssh免密互访、关闭防火墙、关闭swap、禁用SELINUX</strong></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 关闭防火墙
systemctl stop firewalld.service
systemctl disable firewalld.service
# 关闭swap
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
# 禁用SELINUX
sed -i s/^SELINUX=.*$/SELINUX=disabled/ /etc/selinux/config
# 创建key
ssh-keygen -t rsa</code></pre>
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 每个节点执行完上述命令后再执行ssh复制，每个节点都要执行N次（N为节点数-1）
ssh-copy-id -i ~/.ssh/id_rsa.pub root@k8s-X</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="docker"><a class="anchor" href="#docker"></a>4.3.3. Docker</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://kubernetes.io/docs/setup/cri/#docker" class="bare">https://kubernetes.io/docs/setup/cri/#docker</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2

yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

yum update -y &amp;&amp; yum install -y docker-ce-18.06.2.ce

mkdir /etc/docker

cat &gt; /etc/docker/daemon.json &lt;&lt;EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true"
  ]
}
EOF


# 添加代理（可选）
mkdir -p /etc/systemd/system/docker.service.d/
cat &gt;&gt;/etc/systemd/system/docker.service.d/http-proxy.conf &lt;&lt;EOF
[Service]
Environment="HTTP_PROXY=http://&lt;代理host&gt;:&lt;代理端口&gt;" "HTTPS_PROXY=http://&lt;代理host&gt;:&lt;代理端口&gt;" "NO_PROXY=localhost,127.0.0.1,dew.ms"
EOF

systemctl daemon-reload
systemctl restart docker
systemctl enable docker</code></pre>
</div>
</div>
<div class="paragraph">
<p>dew-maven-plugin 需要调用 docker 服务，推荐使用独立的一个Docker节点，暴露 DockerD 服务。</p>
</div>
<div class="listingblock">
<div class="title">暴露DOCKER_HOST</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 详见 https://docs.docker.com/config/daemon/

mkdir -p /etc/systemd/system/docker.service.d/
vi /etc/systemd/system/docker.service.d/docker.conf
-
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
-
vi /etc/docker/daemon.json
-
{
  "hosts":[
    "unix:///var/run/docker.sock",
    "tcp://0.0.0.0:2375"
  ]
}
systemctl daemon-reload
systemctl restart docker
-</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="kubernetes"><a class="anchor" href="#kubernetes"></a>4.3.4. Kubernetes</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://kubernetes.io/docs/setup/independent/install-kubeadm/" class="bare">https://kubernetes.io/docs/setup/independent/install-kubeadm/</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 使用阿里云镜像加速下载
cat &lt;&lt;EOF &gt; /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
        http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

cat &lt;&lt;EOF &gt;  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

sysctl --system

yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
systemctl enable --now kubelet

# 查看安装的Kubernetes版本
yum list installed | grep kube</code></pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/" class="bare">https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">Master安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 安装Git，后续会用到
yum install -y git

# 初始化Kubernetes，二选一，使用代理方式
kubeadm init \
    --pod-network-cidr=10.244.0.0/16

# 初始化Kubernetes，二选一，不使用代理方式，通过image-repository 及 --kubernetes-version 避免被墙，注意版本与yum安装的版本对应
kubeadm init \
    --image-repository registry.aliyuncs.com/google_containers \
    --kubernetes-version v1.14.1 \
    --pod-network-cidr=10.244.0.0/16

# 记录上述操作输出中的kubeadm join
# e.g.
# kubeadm join 10.200.10.10:6443 --token i3i7qw.2gst6kayu1e8ezlg --discovery-token-ca-cert-hash sha256:cabc90823a8e0bcf6e3bf719abc569a47c186f6cfd0e156ed5a3cd5a8d85fab0

mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

# 查看集群状态
kubectl get cs

# 安装flannel
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/a70459be0084506e4ec919aa1c114638878db11b/Documentation/kube-flannel.yml

# 都为Running后表示完成
kubectl get pods --all-namespaces

# 创建命名空间，方便后文使用
kubectl create ns devops</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="title">Master做为Node</div>
<div class="paragraph">
<p>默认情况下 master 不会做为 node 节点，可通过此命令强制启用（不推荐）</p>
</div>
<div class="paragraph">
<p><code>kubectl taint nodes --all node-role.kubernetes.io/master-</code></p>
</div>
</td>
</tr>
</table>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/" class="bare">https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">Node安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 执行上一步输出的 kubeadm join ...

# 完成后在master上执行情况如下（以1.14.1版本为例）
kubectl get no
-
k8s-master1   Ready    master   22m     v1.14.1
k8s-node1     Ready    &lt;none&gt;   11m     v1.14.1
k8s-node2     Ready    &lt;none&gt;   8m54s   v1.14.1
k8s-node3     Ready    &lt;none&gt;   8m51s   v1.14.1
k8s-node4     Ready    &lt;none&gt;   8m49s   v1.14.1
-</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">Master HA配置</div>
<div class="content">
<pre># @see https://kubernetes.io/docs/setup/independent/high-availability/</pre>
</div>
</div>
<div class="listingblock">
<div class="title">Node功能划分（打label）</div>
<div class="content">
<pre>kubectl label nodes k8s-nodeX k8s-nodeX ...  group=app
kubectl label nodes k8s-nodeX k8s-nodeX ...  group=devops</pre>
</div>
</div>
<div class="listingblock">
<div class="title">添加外部DNS服务，如dnsmasq</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 编辑Kubernetes的DNS，加上dew.ms的代理
kubectl -n kube-system edit cm coredns
-
data:
  Corefile: |
    ...
    dew.ms:53 {
        errors
        cache 30
        proxy . x.x.x.x
    }
-</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="helm"><a class="anchor" href="#helm"></a>4.3.5. Helm</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://docs.helm.sh/using_helm/#installing-helm" class="bare">https://docs.helm.sh/using_helm/#installing-helm</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash

cat &lt;&lt;EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: tiller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: tiller
    namespace: kube-system
EOF

# 初始化服务，二选一，使用代理方式
helm init --service-account tiller

# 初始化服务，二选一，不使用代理方式，需要指定镜像，注意tiller版本和helm版本对应
helm init --service-account tiller -i registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.13.1
# 或者初始化之后更换镜像
kubectl set image deployment/tiller-deploy tiller=registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.13.1 -n kube-system

# 查看helm版本
helm version

kubectl get pod -n kube-system -l app=helm</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="metrics_server"><a class="anchor" href="#metrics_server"></a>4.3.6. Metrics Server</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
若要使用HPA(Horizontal Pod Autoscaler)，需要安装 resource metrics API,官方推荐 Metrics Server<br>
@see <a href="https://github.com/kubernetes-incubator/metrics-server" class="bare">https://github.com/kubernetes-incubator/metrics-server</a><br>
helm chart: <a href="https://github.com/helm/charts/tree/master/stable/metrics-server" class="bare">https://github.com/helm/charts/tree/master/stable/metrics-server</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">git clone https://github.com/kubernetes-incubator/metrics-server.git
# Kuberneters V1.8以上执行
kubectl create -f metrics-server/deploy/1.8+/
# Fix issue: https://github.com/kubernetes-incubator/metrics-server/issues/131
kubectl patch deploy metrics-server -n kube-system -p "
spec:
  template:
    spec:
      containers:
      - name: metrics-server
        command:
        - /metrics-server
        - --kubelet-insecure-tls
        - --kubelet-preferred-address-types=InternalIP"
# 验证安装结果
kubectl top node
kubectl top pod</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="nginx_ingress_controller"><a class="anchor" href="#nginx_ingress_controller"></a>4.3.7. Nginx Ingress Controller</h4>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 不使用代理，添加 --set defaultBackend.image.repository=registry.cn-hangzhou.aliyuncs.com/google_containers/defaultbackend
# 使用如下方式将80 443暴露出来
helm install stable/nginx-ingress --name dew-nginx --namespace nginx-ingress --version=1.4.0 \
    --set controller.kind=DaemonSet \
    --set controller.hostNetwork=true \
    --set controller.stats.enabled=true \
    --set controller.metrics.enabled=true \
    --set nodeSelector.group=devops</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="dashboard"><a class="anchor" href="#dashboard"></a>4.3.8. Dashboard</h4>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">cat &lt;&lt;EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-certs
  namespace: kube-system
type: Opaque
EOF

# 安装，不使用代理方式需要加上 --set image.repository=registry.cn-hangzhou.aliyuncs.com/google_containers/kubernetes-dashboard-amd64
helm install stable/kubernetes-dashboard --name dew-dashboard --namespace kube-system --version=1.4.0 \
    --set rbac.clusterAdminRole=true \
    --set serviceAccount.create=true \
    --set ingress.enabled=true \
    --set-string ingress.annotations."nginx\.ingress\.kubernetes\.io/backend-protocol"="HTTPS" \
    --set ingress.hosts={dashboard.dew.ms} \
    --set ingress.tls[0].hosts={dashboard.dew.ms},ingress.tls[0].secretName=kubernetes-dashboard-certs \
    --set nodeSelector.group=devops

# 获取Token
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep dew-dashboard-kubernetes-dashboard | awk '{print $1}')

# 添加域名到客户机hosts并访问 https://dashboard.dew.ms</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="elasticsearch"><a class="anchor" href="#elasticsearch"></a>4.3.9. Elasticsearch</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://github.com/elastic/helm-charts/blob/master/elasticsearch" class="bare">https://github.com/elastic/helm-charts/blob/master/elasticsearch</a> 注意仔细查看各参数设值的说明。
     * 暂不推荐Elasticsearch使用容器化。
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">app=dew-elasticsearch
size=200Gi

# 使用NFS存储，创建PV

# 根据replicas的个数来决定下面PV的创建个数
# 在NFS节点中创建NFS目录
for i in {0..1}; do
mkdir -p /data/nfs/elasticsearch/${i}
chmod 775 /data/nfs/elasticsearch/${i}
done

# 在Kubernetes Master节点中创建PV
for i in {0..1}; do
cat &lt;&lt;EOF | kubectl -n devops apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
  labels:
    app: ${app}
  name: ${app}-${i}
spec:
  capacity:
    storage: ${size}
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /data/nfs/elasticsearch/${i}
    server: nfs.dew.ms
EOF
done

# 使用helm安装
helm repo add elastic https://helm.elastic.co

# 若使用 StorageClass，只需设置 --set volumeClaimTemplate.storageClassName="yourscname" 无需创建PV
# TIP : 请注意chart版本及对应参数名的变更
helm install --name dew-elasticsearch elastic/elasticsearch --namespace devops --version=6.5.0 \
    --set imageTag=6.6.1 \
    --set clusterName=dew-elasticsearch \
    --set nodeGroup=master \
    --set masterService=dew-elasticsearch-master \
    --set replicas=2 \
    --set minimumMasterNodes=2 \
    --set volumeClaimTemplate.storageClassName="" \
    --set volumeClaimTemplate.resources.requests.storage=200Gi \
    --set fsGroup=0 \
    --set clusterHealthCheckParams="" \
    --set ingress.enabled=true \
    --set ingress.hosts={es.dew.ms} \
    --set nodeSelector.group=devops</code></pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
其他Elasticsearch的helm chart : <a href="https://github.com/helm/charts/tree/master/stable/elasticsearch" class="bare">https://github.com/helm/charts/tree/master/stable/elasticsearch</a>
</td>
</tr>
</table>
</div>
</div>
<div class="sect3">
<h4 id="kibana"><a class="anchor" href="#kibana"></a>4.3.10. Kibana</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://github.com/helm/charts/tree/master/stable/kibana" class="bare">https://github.com/helm/charts/tree/master/stable/kibana</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 使用NFS存储，创建PV &amp; PVC

# 在NFS节点中创建NFS目录
mkdir -p /data/nfs/kibana

# 在Kubernetes Master节点中创建PV &amp; PVC
cat &lt;&lt;EOF | kubectl -n devops apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
  labels:
    app: kibana
  name: dew-kibana
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /data/nfs/kibana
    server: nfs.dew.ms
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  labels:
    app: kibana
  name: kibana
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  selector:
    matchLabels:
      app: kibana
EOF

# 若使用 StorageClass，则无需创建PV，PVC，需设置以下参数
# 将existingClaim的值改为false    --set persistentVolumeClaim.existingClaim=false
# 设置StorageClass的名字    --set persistentVolumeClaim.storageClass="yourscname"
# 设置size    --set persistentVolumeClaim.size=10Gi
helm install --name dew-kibana stable/kibana --namespace devops --version=2.2.0 \
    --set image.tag="6.6.1" \
    --set env."ELASTICSEARCH_URL"="http://dew-elasticsearch-master:9200" \
    --set service.internalPort=5601 \
    --set ingress.enabled=true,ingress.hosts={kibana.dew.ms} \
    --set dashboardImport.enabled=true \
    --set dashboardImport.dashboards."k8s"="https://raw.githubusercontent.com/monotek/kibana-dashboards/master/k8s-fluentd-elasticsearch.json" \
    --set serviceAccount.create=true,serviceAccountName=kibana \
    --set plugins.enabled=true \
    --set persistentVolumeClaim.enabled=true \
    --set persistentVolumeClaim.existingClaim=true \
    --set securityContext.enabled=true \
    --set securityContext.allowPrivilegeEscalation=true \
    --set securityContext.runAsUser=0 \
    --set securityContext.fsGroup=0 \
    --set nodeSelector.group=devops</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="prometheus_grafana"><a class="anchor" href="#prometheus_grafana"></a>4.3.11. Prometheus &amp; Grafana</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://github.com/helm/charts/tree/master/stable/prometheus-operator" class="bare">https://github.com/helm/charts/tree/master/stable/prometheus-operator</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">prometheus-operator结构</div>
<div class="content">
<pre> |--- prometheus-operator
 |--- prometheus
 |--- alertmanager
 |--- node-exporter
 |--- kube-state-metrics
 |--- service monitors to scrape internal kubernetes components
 |     |---kube-apiserver
 |     |---kube-scheduler
 |     |---kube-controller-manager
 |     |---etcd
 |     |---kube-dns/coredns
 |--- grafana</pre>
</div>
</div>
<div class="listingblock">
<div class="title">创建prometheus 的 PV</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">app=prometheus
components=("alertmanager" "prometheus")
size=100Gi

# 在NFS节点中创建NFS目录
for i in ${components[@]};do
mkdir -p /data/nfs/${app}/${i}
done

# 在Kubernetes Master节点中创建PV
for i in ${components[@]};do
cat &lt;&lt;EOF | kubectl -n devops apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
  labels:
    component: ${i}
  name: ${app}-${i}
spec:
  capacity:
    storage: ${size}
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /data/nfs/${app}/${i}
    server: nfs.dew.ms
EOF
done</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">创建grafana 的 PV &amp; PVC</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 在NFS节点中创建NFS目录
mkdir -p /data/nfs/grafana

# 在Kubernetes Master节点中创建PV &amp; PVC
cat &lt;&lt;EOF | kubectl -n devops apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
  labels:
    app: grafana
  name: grafana
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /data/nfs/grafana
    server: nfs.dew.ms
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  labels:
    app: grafana
  name: grafana
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
  selector:
    matchLabels:
      app: grafana
EOF</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">使用helm安装</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 若需要对etcd进行监控，则需要先创建secret
kubectl -n devops create secret generic dew-prometheus-operator-etcd \
  --from-file=/etc/kubernetes/pki/etcd/ca.crt \
  --from-file=/etc/kubernetes/pki/etcd/peer.crt \
  --from-file=/etc/kubernetes/pki/etcd/peer.key

# 安装
# · 不使用代理要加上 --set kube-state-metrics.image.repository=registry.cn-hangzhou.aliyuncs.com/google_containers/kube-state-metrics
# · 若要启用对etcd监控，需设置kubeEtcd相关参数。
# · grafana.'grafana\.ini'为Grafana的配置参数,请安装时自行修改。
# · 使用StorageClass
#    - 若 Grafana 使用StorageClass，需要进行以下配置，无需创建PV，PVC
#      --set grafana.persistence.storageClassName="yourscname"
#      --set grafana.persistence.size=10Gi
#      删去此行配置：    --set grafana.persistence.existingClaim=grafana \
#    - Prometheus使用StorageClass,需要添加以下配置，无需创建PV，PVC
#      --set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.storageClassName="yourscname" \
#    - Alertmanager和Prometheus使用StorageClass,需要添加以下配置，无需创建PVC，但因为自动生成的PVC名称过长，
#      而无法动态创建PV，所以需要自行修改chart相关配置或指定已创建好的PV名称：
#      --set alertmanager.alertmanagerSpec.storage.volumeClaimTemplate.spec.volumeName=prometheus-alertmanager \
#      --set alertmanager.alertmanagerSpec.storage.volumeClaimTemplate.spec.storageClassName="yourscname" \
helm install stable/prometheus-operator --name dew-prometheus-operator --namespace devops --version=5.0.10 \
    --set kubelet.serviceMonitor.https=true \
    --set prometheus.ingress.enabled=true \
    --set prometheus.ingress.hosts={prometheus.dew.ms} \
    --set alertmanager.ingress.enabled=true \
    --set alertmanager.ingress.hosts={alertmanager.dew.ms} \
    --set prometheusOperator.securityContext.runAsNonRoot=false \
    --set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=100Gi \
    --set alertmanager.alertmanagerSpec.storage.volumeClaimTemplate.spec.resources.requests.storage=100Gi \
    --set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.selector.matchLabels."component"="prometheus" \
    --set alertmanager.alertmanagerSpec.storage.volumeClaimTemplate.spec.selector.matchLabels."component"="alertmanager" \
    --set prometheus.prometheusSpec.secrets[0]=dew-prometheus-operator-etcd \
    --set kubeEtcd.serviceMonitor.scheme=https \
    --set kubeEtcd.serviceMonitor.insecureSkipVerify=true \
    --set kubeEtcd.serviceMonitor.caFile="/etc/prometheus/secrets/dew-prometheus-operator-etcd/ca.crt" \
    --set kubeEtcd.serviceMonitor.certFile="/etc/prometheus/secrets/dew-prometheus-operator-etcd/peer.crt" \
    --set kubeEtcd.serviceMonitor.keyFile="/etc/prometheus/secrets/dew-prometheus-operator-etcd/peer.key" \
    --set grafana.enabled=true \
    --set grafana.adminPassword=Dew123456 \
    --set grafana.defaultDashboardsEnabled=true \
    --set grafana.ingress.enabled=true \
    --set grafana.ingress.hosts={grafana.dew.ms} \
    --set grafana.sidecar.dashboards.enabled=true \
    --set grafana.sidecar.dashboards.searchNamespace="devops"\
    --set grafana.sidecar.dashboards.label=grafana_dashboard \
    --set grafana.sidecar.datasources.enabled=true \
    --set grafana.sidecar.datasources.searchNamespace="devops" \
    --set grafana.sidecar.datasources.label=grafana_datasource \
    --set grafana.'grafana\.ini'.smtp.enabled="true" \
    --set grafana.'grafana\.ini'.smtp.host="smtp.163.com:25" \
    --set grafana.'grafana\.ini'.smtp.user=XXXXX@163.com \
    --set grafana.'grafana\.ini'.smtp.password=XXXXX \
    --set grafana.'grafana\.ini'.smtp.from_address="XXXXX@163.com" \
    --set grafana.'grafana\.ini'.smtp.skip_verify=true \
    --set grafana.'grafana\.ini'.server.root_url="http://grafana.dew.ms" \
    --set grafana.persistence.enabled=true \
    --set grafana.persistence.existingClaim=grafana \
    --set prometheusOperator.nodeSelector.group=devops \
    --set alertmanager.alertmanagerSpec.nodeSelector.group=devops \
    --set prometheus.prometheusSpec.nodeSelector.group=devops \
    --set kube-state-metrics.nodeSelector.group=devops \
    --set nodeExporter.nodeSelector.group=devops \
    --set grafana.nodeSelector.group=devops


# grafana默认用户名：admin
# 访问 http://prometheus.dew.ms
# 访问 http://alertmanager.dew.ms
# 访问 http://grafana.dew.ms</code></pre>
</div>
</div>
<div class="quoteblock">
<div class="title">常见问题</div>
<blockquote>
<div class="paragraph">
<p>如何查看设置的密码</p>
</div>
</blockquote>
</div>
<div class="literalblock">
<div class="content">
<pre>kubectl get secret --namespace devops dew-prometheus-operator-grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo</pre>
</div>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>如何重置grafana密码</p>
</div>
</blockquote>
</div>
<div class="literalblock">
<div class="content">
<pre>进入grafana的容器内部后执行
grafana-cli admin reset-admin-password passwordvalue</pre>
</div>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>有pod启动失败,报文件权限拒绝相关问题，如 "opening storage failed: create dir: mkdir /prometheus/wal: permission denied"</p>
</div>
</blockquote>
</div>
<div class="literalblock">
<div class="content">
<pre>很可能和PV的文件目录的权限有关，检查下权限是否一致，设置对应的securityContext进行排查。例：
kubectl edit statefulset prometheus-dew-prometheus-operator-prometheus -n devops
设置securityContext为以下内容
-
 securityContext:
   fsGroup: 0
   runAsNonRoot: false
   runAsUser: 0
-</pre>
</div>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>通过UI查看prometheus的target中，kube-scheduler、kube-controller处于down状态</p>
</div>
</blockquote>
</div>
<div class="literalblock">
<div class="content">
<pre>因为它们只能在宿主机上通过127.0.0.1访问，可使用以下操作：
. 如果使用kubeadm启动的集群，初始化时的config.yml里可以加入如下参数
    controllerManagerExtraArgs:
      address: 0.0.0.0
    schedulerExtraArgs:
      address: 0.0.0.0
. 已经启动后的使用下面命令更改就会滚动更新
    sed -e "s/- --address=127.0.0.1/- --address=0.0.0.0/" -i /etc/kubernetes/manifests/kube-controller-manager.yaml
    sed -e "s/- --address=127.0.0.1/- --address=0.0.0.0/" -i /etc/kubernetes/manifests/kube-scheduler.yaml
  或者全部替换：
    sed -ri '/--address/s#=.+#=0.0.0.0#' /etc/kubernetes/manifests/kube-*
. 参考文章：
  http://www.servicemesher.com/blog/prometheus-operator-manual/
  https://github.com/coreos/prometheus-operator/blob/master/Documentation/troubleshooting.md</pre>
</div>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>如何监控APP</p>
</div>
</blockquote>
</div>
<div class="literalblock">
<div class="content">
<pre>1.首先需要将项目instrument
  参考文章：https://prometheus.io/docs/instrumenting/clientlibs/
2.部署项目及创建进行监控的ServiceMonitor
  注意ServiceMonitor的labels要含有Prometheus-operator创建的Prometheus的serviceMonitorSelector的label。
  详细文章：https://github.com/coreos/prometheus-operator/blob/master/Documentation/user-guides/getting-started.md#related-resources
            https://coreos.com/operators/prometheus/docs/latest/user-guides/getting-started.html
3.Demo
  以devops-example-todo的compute模块为例创建ServiceMonitor
  link: https://github.com/dew-ms/devops-example-todo
  -
  cat &lt;&lt;EOF | kubectl -n devops apply -f -
  apiVersion: monitoring.coreos.com/v1
  kind: ServiceMonitor
  metadata:
    labels:
      app: todo-compute
      release: dew-prometheus-operator
    name: dew-app-todo-compute
  spec:
    endpoints:
    - honorLabels: true
      interval: 10s
      path: /actuator/prometheus
      port: http
    jobLabel: dew-app-todo-compute
    namespaceSelector:
      matchNames:
      - dew-uat
    selector:
      matchLabels:
        app: todo-compute
  EOF
  -</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="fluentd"><a class="anchor" href="#fluentd"></a>4.3.12. Fluentd</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://github.com/kiwigrid/helm-charts/tree/master/charts/fluentd-elasticsearch" class="bare">https://github.com/kiwigrid/helm-charts/tree/master/charts/fluentd-elasticsearch</a><br>
     <a href="https://kiwigrid.github.io/" class="bare">https://kiwigrid.github.io/</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">helm repo add kiwigrid https://kiwigrid.github.io

# 安装
# · 不使用代理要加上 --set image.tag=v2.4.0 --set image.repository=registry.cn-hangzhou.aliyuncs.com/google_containers/fluentd-elasticsearch
#    如需使用最新版本镜像，可自行下载镜像源文件制作镜像
#    地址：https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/fluentd-elasticsearch/fluentd-es-image
# · 镜像版本建议使用v2.5.2以上，避免一些bug。
# · 请根据需要进行节点亲和性相关设置，但请保证需要收集日志的节点有Fluentd部署。
#    e.g. 若只需收集部署了应用的节点的日志，设置 --set nodeSelector.group=app
# · 通过启用configMaps.useDefaults各类型的日志配置文件，来实现各类日志的收集，默认为全部启用。
#    更多的日志收集配置，请见：https://docs.fluentd.org
# · 若要启用Prometheus进行监控Fluentd，
#    需要先将Fluentd通过设置service暴露出来，然后设置prometheusRule和serviceMonitor。
#    此配置需结合Prometheus-operator使用。
helm install kiwigrid/fluentd-elasticsearch --name dew-fluentd-es --namespace devops --version=3.0.0 \
    --set elasticsearch.host=dew-elasticsearch-master \
    --set elasticsearch.port=9200 \
    --set elasticsearch.logstash_prefix=logstash \
    --set service.type=ClusterIP \
    --set service.ports[0].name="monitor-agent" \
    --set service.ports[0].port=24231 \
    --set prometheusRule.enabled=true \
    --set prometheusRule.prometheusNamespace=devops \
    --set prometheusRule.labels.app=prometheus-operator \
    --set prometheusRule.labels.release=dew-prometheus-operator \
    --set serviceMonitor.enabled=true \
    --set serviceMonitor.labels.release=dew-prometheus-operator \
    --set configMaps.useDefaults.outputConf=true \
    --set configMaps.useDefaults.containersInputConf=true \
    --set configMaps.useDefaults.systemConf=false \
    --set configMaps.useDefaults.systemInputConf=false \
    --set configMaps.useDefaults.forwardInputConf=false \
    --set configMaps.useDefaults.monitoringConf=false</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="jaeger"><a class="anchor" href="#jaeger"></a>4.3.13. Jaeger</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://github.com/jaegertracing/jaeger-operator" class="bare">https://github.com/jaegertracing/jaeger-operator</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/crds/jaegertracing_v1_jaeger_crd.yaml
curl https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/service_account.yaml \
    | sed "s/namespace: observability/namespace: devops/g" \
    | kubectl create -f -
curl https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/service_account.yaml \
    | sed "s/namespace: observability/namespace: devops/g" \
    | kubectl create -f -
curl https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role.yaml \
    | sed "s/namespace: observability/namespace: devops/g" \
    | kubectl create -f -
curl https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role_binding.yaml \
    | sed "s/namespace: observability/namespace: devops/g" \
    | kubectl create -f -
curl https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/operator.yaml \
    | sed "s/namespace: observability/namespace: devops/g" \
    | kubectl create -f -

# 创建Jaeger实例
cat &lt;&lt;EOF | kubectl apply -n devops -f -
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: jaeger
spec:
  strategy: production
  storage:
    type: elasticsearch
    options:
      es:
        server-urls: http://dew-elasticsearch-master:9200
  ingress:
    enabled: false # 用于使用下面自定义的Ingress
EOF

# Jaeger实例可在不同namespace下创建使用，使用中请注意namespace的问题。
# 使用sidecar的方式部署项目：https://github.com/jaegertracing/jaeger-operator#auto-injection-of-jaeger-agent-sidecars
# 使用daemonset的方式部署项目：https://github.com/jaegertracing/jaeger-operator#agent-as-daemonset

# 添加Host，为Jaeger实例创建Ingress
# 注意serviceName与Jaeger实例创建的service名称保持一致
cat &lt;&lt;EOF | kubectl -n devops apply -f -
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
  name: jaeger-query
spec:
  rules:
    - host: jaeger.dew.ms
      http:
        paths:
          - backend:
              serviceName: jaeger-query
              servicePort: 16686
            path: /
EOF

# Pod的调度
# 目前jaeger-operator暂不支持直接设置，请关注该项目的更新情况。
# 可以自行给需要调度的pod的deployment添加限制条件。
# e.g.
 kubectl patch deploy jaeger-operator jaeger-collector jaeger-query -n devops -p '{"spec": {"template": {"spec": {"nodeSelector": {"group": "devops"}}}}}'</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">使用</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 在Deployment 和 Service 中添加annotations : sidecar.jaegertracing.io/inject: "true" 即可。
# 使用Dew的 devops-maven-plugin 部署会自动添加此注解。

# 手工添加示例如下：
cat &lt;&lt;EOF | kubectl apply -f -
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    # 添加 Jaeger的注解
    sidecar.jaegertracing.io/inject: "true"
  name: jaeger-demo
spec:
  template:
    metadata:
      labels:
        app: jaeger-demo
        version: v1
    spec:
      containers:
      - name: jaeger-demo
        image: jaegertracing/example-hotrod:1.10
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  annotations:
   # 添加 Jaeger的注解
    sidecar.jaegertracing.io/inject: "true"
  name: jaeger-demo
  labels:
    app: jaeger-demo
spec:
  ports:
   - name: jaeger-demo
     port: 8080
     targetPort: 8080
  selector:
   app: jaeger-demo
EOF</code></pre>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="devops-configuration"><a class="anchor" href="#devops-configuration"></a>4.4. DevOps配置速查</h3>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<div class="paragraph">
<p>DevOps的配置有两类，
一是通过项目目录下的 <code>.dew</code> 配置，
二是通过 <code>Maven</code> 配置，在 <code>pom.xml</code> 中的properties或是 <code>mvn -Dxxx</code> 作为参数传入。</p>
</div>
<div class="paragraph">
<p>推荐的做法是相对固定的、不敏感的信息配置到 <code>.dew</code> 中，而诸如Kubernetes配置、Harbor用户名/密码等信息用Maven在命令行中作为参数传入。</p>
</div>
</td>
</tr>
</table>
</div>
<div class="sect3">
<h4 id="devops-configuration-dew"><a class="anchor" href="#devops-configuration-dew"></a>4.4.1. .dew 配置</h4>
<div id="devops-configuration-dew-inheritance-rules" class="ulist">
<div class="title">继承规则</div>
<ul>
<li>
<p><code>.dew</code> 可放在任意应用（模块）的根目录（与pom.xml同级）下，应用会继承项目的配置</p>
</li>
<li>
<p><code>.dew</code> 支持多环境(profiles)，各环境继承默认环境的配置</p>
</li>
</ul>
</div>
<div class="listingblock">
<div class="title">继承示例</div>
<div class="content">
<pre># 项目结构
|- moduleA
|-  |- pom.xml
|-  |- .dew         # 应用A配置
|- pom.xml
|- .dew             # 全局配置

# 全局配置的.dew 内容:
-
app:
  replicas: 1
profiles:
  test:
    namespace: todo-test
  uat:
    namespace: todo-uat
-
# 应用A配置的.dew 内容:
-
profiles:
  uat:
    app:
      replicas: 2
-

# 对于应用A最终的配置为:
-
app:
  replicas: 1               # 继承全局配置
profiles:
  test:
    namespace: todo-test    # 继承全局配置
    app:
      replicas: 1           # 继承全局配置的默认环境配置
  uat:
    namespace: todo-uat
    app:
      replicas: 2           # 使用应用A的覆写配置
-</pre>
</div>
</div>
<div class="ulist">
<div class="title">profiles 继承规则</div>
<ul>
<li>
<p>若全局配置的<code>.dew</code>中配置了某一profile的参数，若想在应用B覆写全局配置的profile，则需要在应用B中设定此配置以实现覆写。</p>
</li>
</ul>
</div>
<div class="listingblock">
<div class="content">
<pre># 项目结构
|- moduleA
|-  |- pom.xml
|-  |- .dew         # 应用A配置
|- moduleB
|-  |- pom.xml
|-  |- .dew         # 应用A配置
|- pom.xml
|- .dew             # 全局配置

# 全局配置的.dew 内容:
-
app:
  replicas: 1 # 全局默认配置
profiles:
  test:
    namespace: todo-test
  uat:
    namespace: todo-uat
    app:
      replicas: 2 # 全局profile配置
-
# 应用A配置的.dew 内容:
-
profiles:
  uat:
    app:
      replicas: 3 # 应用A的profile配置
-
# 应用B配置的.dew 内容:
-
app:
  replicas: 4     # 应用B的默认配置
profiles:
  uat:
    app:
      replicas: 5 # 应用B的profile配置
-

# 对于应用A最终的配置为:
-
app:
  replicas: 1               # 继承全局配置
profiles:
  test:
    namespace: todo-test    # 继承全局配置
    app:
      replicas: 1           # 继承全局配置的默认环境配置
  uat:
    namespace: todo-uat
    app:
      replicas: 3           # 使用应用A的profile覆写配置
-

# 对于应用B最终的配置为:
-
app:
  replicas: 4               # 使用应用B的默认配置
profiles:
  test:
    namespace: todo-test    # 继承全局配置
    app:
      replicas: 4           # 使用应用B的默认配置
  uat:
    namespace: todo-uat
    app:
      replicas: 5           # 使用应用B的profile覆写配置
-</pre>
</div>
</div>
<div class="listingblock">
<div class="title">配置说明</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yaml" data-lang="yaml"># 默认环境配置
namespace: default                      # 命名空间，强烈建议修改为特定的命名空间
skip: false                             # 是否跳过，为true时表示
disableReuseVersion:                    # 是否禁用重用版本，e.g. 生产环境重用预发环境的最后一个版本（不用重新打包Docker image)
                                        # 默认情况前端工程为true（node编译期会混入环境信息导致无法重用），其它工程为false
reuseLastVersionFromProfile:            # 重用版本的目标环境名称
                                        # 默认会尝试使用 pre-prod/pre-production/uat 为名称（找到当前项目第一个存在的环境）
                                        # 都不存在时需要显式指定
ignoreChangeFiles: []                   # 忽略变更文件列表，此列表指定的文件不用于是否有变更要部署的判断依据
                                        # 支持 glob , @see https://en.wikipedia.org/wiki/Glob_(programming)
app:                                    # 应用配置
  replicas: 1                           # 部署的副本数
  revisionHistoryLimit: 3               # 保留的历史版本数
  port: 8080                            # 端口号，默认情况下前端应用为80(不可修改)，后端服务为8080
  livenessPath: /actuator/health        # 存活检测HTTP的路径，仅用于后端服务
  readinessPath: /actuator/health       # 可用检测HTTP的路径，仅用于后端服务
  livenessInitialDelaySeconds: 60       # 首次存活检测延迟时间，仅用于后端服务
  livenessPeriodSeconds: 30             # 存活检测周期，仅用于后端服务
  livenessFailureThreshold: 6           # 存活检测失败次数阈值，超过后销毁当前实例并重启另一个实例，仅用于后端服务
  readinessInitialDelaySeconds: 10      # 首次可用检测延迟时间，仅用于后端服务
  readinessPeriodSeconds: 60            # 可用检测周期，仅用于后端服务
  readinessFailureThreshold: 3          # 可用检测失败次数阈值，超过后当前实例不可用，仅用于后端服务
  traceLogEnabled: true                 # 是否启用追踪日志，仅用于后端服务
  traceLogSpans: false                  # 是否在控制台输出spans日志，仅用于后端服务
  traceWebSkipPattern: ""               # 设置跳过追踪的接口，为空则使用官方默认值，仅用于后端服务
  traceProbabilisticSamplingRate: 0.1   # 追踪日志概率采样比率，为1.0则使用全量采样，仅用于后端服务
  metricsEnabled: true                  # 是否启用Prometheus的metrics，仅用于后端服务
  nodeSelector:                         # 节点亲和性配置
    group: app                          # 默认选择标签为 group=app 的节点
  preparePackageCmd:                    # 预打包命令
                                        # 前端应用默认为 cd &lt;应用目录&gt; &amp;&amp; set NODE_ENV=&lt;环境名称&gt; &amp;&amp; npm install，发现不存在 node_modules 时执行
                                        # 后端服务默认为空
  packageCmd:                           # 打包命令
                                        # 前端应用默认为 cd &lt;应用目录&gt; &amp;&amp; set NODE_ENV=&lt;环境名称&gt; npm run build:&lt;环境名称&gt;
                                        # 后端服务默认为空
  runOptions:                           # 运行参数，可指定诸如 JVM 配置等信息
  serverConfig: |-                      # 服务配置，多为Nginx配置
  containerResourcesLimits: {}          # 容器资源上限，同Kubernetes配置，值应大于等于containerResourcesRequests
                                        # @see  https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/
  containerResourcesRequests: {}        # 容器资源下限，同Kubernetes配置，值应小于等于containerResourcesLimits
                                        # @see  https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/
docker:                                 # Docker配置
  host:                                 # Dockerd的Host e.g. tcp://dockerd.dew.ms:2375
  registryUrl:                          # Docker Registry Url  e.g. https://harbor.dew.ms/v2
  registryUserName:                     # Docker Registry 用户名，此项建议使用Maven方式配置
  registryPassword:                     # Docker Registry 密码，此项建议使用Maven方式配置
  image:                                # Docker 镜像
                                        # 前端应用默认使用 nginx:alpine
                                        # 后端服务应用默认使用 openjdk:8-alpine
kube:                                   # Kubernetes配置
  base64Config:                         # Kubernetes Base64 后的配置，此项建议使用Maven方式配置
                                        # 使用 ``echo $(cat ~/.kube/config | base64) | tr -d " "`` 获取
notifies:                               # 通知配置
  - type: DD                            # 通知的类型，DD=钉钉 MAIL=邮件，邮件方式需要有配置spring.mail下相关的smtp信息 HTTP=自定义HTTP Hook
    defaultReceivers: []                # 默认接收人列表，钉钉为手机号，邮件为邮箱
    dndTimeReceivers: []                # 免扰时间内的接收人列表，只有该列表中的接收人才能在免扰时间内接收通知
    args:                               # 不同类型的参数，邮件不需要设置
        url:                            # type=DD表示钉钉的推送地址
                                        # 说明详见：https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq
                                        # type=HTTP表示HTTP Hook的地址
        msgType:                        # 仅用于type=DD，支持 text/markdown
    strategy:                           # 通知策略
        minIntervalSec: 0               # 最小间隔的通知时间，0表示不设置，如为10则表示10s内只会发送一次
        dndTime:                        # 免扰时间，HH:mm-HH:mm 如，18:00-06:00
                                        # HH:mm-HH:mm，如果两个时间相等表示全天免扰，如果后者大于前者表示跨天免扰
        forceSendTimes: 3               # 同一免扰周期间通知调用达到几次后强制发送
# 其它环境配置
profiles:
    &lt;name&gt;: # 环境名称，e.g. test uat prod
        # 此处配置项同默认环境的配置项，用于覆写默认配置</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="maven_配置"><a class="anchor" href="#maven_配置"></a>4.4.2. Maven 配置</h4>
<div class="listingblock">
<div class="title">配置说明</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># ============= 公共场景使用 =============
dew_devops_profile                           # 指定的环境
dew_devops_kube_config                       # Kubernetes Base64 后的配置，使用 ``echo $(cat ~/.kube/config | base64) | tr -d " "`` 获取
# ============= 发布与回滚使用 =============
dew_devops_docker_host                       # Dockerd的Host e.g. tcp://dockerd.dew.ms:2375
dew_devops_docker_registry_url               # Docker Registry Url  e.g. https://harbor.dew.ms/v2
dew_devops_docker_registry_username          # Docker Registry 用户名
dew_devops_docker_registry_password          # Docker Registry 密码
dew_devops_assignation_projects              # 指定部署应用artifactId  多个以逗号分隔
dew_devops_quiet                             # 是否静默处理
# ============= 日志及调试场景使用 =============
dew_devops_podName                           # 要使用的Pod名称，如不填写当存在多个Pod时会要求用户选择
# ============= 日志场景使用 =============
dew_devops_log_follow                        # 是否滚动查看日志
# ============= 调试场景使用 =============
dew_devops_debug_forward_port                # 转发端口标识
# ============= 伸缩场景使用 =============
dew_devops_scale_replicas                    # 伸缩Pod数量
dew_devops_scale_auto                        # 是否启用自动伸缩
dew_devops_scale_auto_minReplicas            # 自动伸缩Pod数下限
dew_devops_scale_auto_maxReplicas            # 自动伸缩Pod数上限
dew_devops_scale_auto_cpu_averageUtilization # 自动伸缩条件：CPU平均使用率标识</code></pre>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="devops-best-practices"><a class="anchor" href="#devops-best-practices"></a>4.5. DevOps最佳实践</h3>
<div class="sect3">
<h4 id="为什么不用_fabric8"><a class="anchor" href="#为什么不用_fabric8"></a>4.5.1. 为什么不用 fabric8</h4>
<div class="paragraph">
<p>Fabric8的maven插件( <a href="https://github.com/fabric8io/docker-maven-plugin" class="bare">https://github.com/fabric8io/docker-maven-plugin</a> )比较成熟，并且被 Spring Cloud Kubernetes 官方推荐( <a href="https://github.com/fabric8-quickstarts/spring-boot-ribbon" class="bare">https://github.com/fabric8-quickstarts/spring-boot-ribbon</a> )。</p>
</div>
<div class="paragraph">
<p><code>dew</code> 之所以没有使用fabric8是因为设计理念有一定差异，<code>dew</code> 追求 <code>轻量化、在一定规范下的可定制化、集成化</code> ，
比如实现了基于环境的部署流程，自动判断应用是否需要发布、版本管理、版本重用模式、钉钉通知等，这些差异是导致自研的原因。
当然在实现上也借鉴了fabric8比较好的做法，比如JVM参数的自动化处理。</p>
</div>
</div>
<div class="sect3">
<h4 id="devops-best-practices-auth"><a class="anchor" href="#devops-best-practices-auth"></a>4.5.2. 鉴权处理</h4>
<div class="paragraph">
<p>在传统的Spring Cloud工程中多使用网关（如 Zuul、Spring Cloud Gateway）做统一的认证处理，但在Kubernetes下使用了 <code>ingress</code> ，如果再加上网关会导致多了一层转发。
并且Kubernetes使用自身的Service已经实现了网关的服务发现，所以再引入网关就显得累赘了。</p>
</div>
<div class="paragraph">
<p>去掉网关后鉴权处理会下沉到各业务组件， <code>dew</code> 提供了基础的鉴权支持 <a href="#framework-user-manual-auth">权限认证</a> 也可使用 Spring Cloud Security 方案实现。</p>
</div>
</div>
<div class="sect3">
<h4 id="统一配置处理"><a class="anchor" href="#统一配置处理"></a>4.5.3. 统一配置处理</h4>
<div class="paragraph">
<p>Spring Cloud Kubernetes 实现了基于ConfigMap/Secrets的统一配置管理，但由于后者暂未解决版本化管理、自定义密钥的加解密及友好的配置编辑能力，
故推荐使用Spring Cloud Config， <code>dew</code> 基于Spring Cloud Config封装了Chart，详见 <a href="#helm-chart-spring-cloud-config">Dew Helm Chart : Spring Cloud Config</a> 。</p>
</div>
<div class="paragraph">
<p>统一配置服务建议多个项目共享一套，为方便开发调试，故此服务建议使用URL调用而非ServiceId，e.g:</p>
</div>
<table class="tableblock frame-all grid-all spread">
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">原配置</th>
<th class="tableblock halign-left valign-top">现配置</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><div><div class="listingblock">
<div class="content">
<pre>spring:
   cloud:
     config:
       discovery:
         service-id: config
         enabled: true</pre>
</div>
</div></div></td>
<td class="tableblock halign-left valign-top"><div><div class="listingblock">
<div class="content">
<pre>spring:
   cloud:
     config:
       uri: http://config.dew.ms</pre>
</div>
</div></div></td>
</tr>
</tbody>
</table>
</div>
<div class="sect3">
<h4 id="jvm设置"><a class="anchor" href="#jvm设置"></a>4.5.4. JVM设置</h4>
<div class="paragraph">
<p>在 <code>app.runOptions</code> 配置中可以指定 JVM 的参数（比如 <code>-Xms -Xmx</code>），但同时也可以通过 <code>app.containerResourcesLimits</code> 和 <code>app.containerResourcesRequests</code> 指定容器资源的上下限，两者如何配合？</p>
</div>
<div class="paragraph">
<p><code>dew</code> 推荐用户设置容器的资源限定，部署时 <code>dew</code> 会根据容器环境自行设置JVM参数，规则如下：</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>-Xmx</code> 或 <code>-Xms</code> 设置</p>
<div class="ulist">
<ul>
<li>
<p>如果传入的 <code>JAVA_OPTIONS</code> 带有 <code>-Xmx</code> 或 <code>-Xms</code> 则优先使用传入设置</p>
</li>
<li>
<p>如果传入了 <code>JAVA_MAX_MEM_RATIO</code> 则设置 <code>-Xmx</code> 为 <code>最大可用内存</code>/<code>JAVA_MAX_MEM_RATIO</code>， 当 <code>JAVA_MAX_MEM_RATIO</code>=50 时表示 <code>-Xmx</code> 为最大可用内存的50%</p>
</li>
<li>
<p>如果没有指定 <code>JAVA_MAX_MEM_RATIO</code> 且 <code>系统内存资源</code> &#8656; 300M 则 设置 <code>-Xmx</code> 为其25%</p>
</li>
<li>
<p>如果没有指定 <code>JAVA_MAX_MEM_RATIO</code> 且 <code>系统内存资源</code> &gt; 300M 则 设置 <code>-Xmx</code> 为其50%</p>
</li>
<li>
<p>如果传入了 <code>JAVA_INIT_MEM_RATIO</code> 并且 <code>JAVA_INIT_MEM_RATIO</code>=0 则不设置 <code>-Xms</code></p>
</li>
<li>
<p>如果传入了 <code>JAVA_INIT_MEM_RATIO</code> 并且 <code>JAVA_INIT_MEM_RATIO</code>!=0 &amp; <code>-Xmx</code> 不为空 则 设置 <code>-Xms</code> 为 <code>-Xmx</code>/<code>JAVA_INIT_MEM_RATIO</code>，当 <code>JAVA_INIT_MEM_RATIO</code>=50 时表示 <code>-Xms</code> 为 <code>-Xmx</code>的50%</p>
</li>
<li>
<p>如果没有传入 <code>JAVA_INIT_MEM_RATIO</code> 则 设置 <code>JAVA_INIT_MEM_RATIO</code>=100，执行同上一步的 <code>-Xms</code> 设置规则</p>
</li>
</ul>
</div>
</li>
<li>
<p>如果JDK版本 &lt; 10 设置 <code>-XX:ParallelGCThreads</code>=&lt;可用CPU核数&gt; <code>-XX:ConcGCThreads</code>=&lt;可用CPU核数&gt; <code>-Djava.util.concurrent.ForkJoinPool.common.parallelism</code>=&lt;可用CPU核数&gt;</p>
</li>
<li>
<p>如果JDK版本 &lt; 10 设置 <code>-XX:+UseParallelGC</code> <code>-XX:GCTimeRatio</code>=4 <code>-XX:AdaptiveSizePolicyWeight</code>=90 <code>-XX:MinHeapFreeRatio</code>=40 <code>-XX:MaxHeapFreeRatio</code>=70</p>
</li>
<li>
<p>如果JDK版本 !=7 设置 <code>-XX:+ExitOnOutOfMemoryError</code></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>以上规则参考于： <a href="https://github.com/fabric8io-images/run-java-sh" class="bare">https://github.com/fabric8io-images/run-java-sh</a></p>
</div>
</div>
<div class="sect3">
<h4 id="配置中心选择"><a class="anchor" href="#配置中心选择"></a>4.5.5. 配置中心选择</h4>
<div class="paragraph">
<p>由于 <code>spring-cloud-kubernetes</code> 使用的配置方案并不直观、无法实现版本留存、修改审计、配置加密（非摘要），
所以 <code>dew</code> 建议使用 <code>Spring Cloud Config</code> ，<code>dew</code> 提供了其 helm 版本，详见 <a href="#helm-chart-spring-cloud-config">Dew Helm Chart : Spring Cloud Config</a> 。</p>
</div>
</div>
<div class="sect3">
<h4 id="spring_cloud_项目的_dew_gitlab_ci_cd_实践"><a class="anchor" href="#spring_cloud_项目的_dew_gitlab_ci_cd_实践"></a>4.5.6. Spring Cloud 项目的 Dew &amp; Gitlab CI/CD 实践</h4>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>部署 Spring Cloud 配置中心，详见 <a href="#helm-chart-spring-cloud-config">Dew Spring Cloud 配置中心部署</a>，<a href="#dew-spring-cloud-config">Dew Spring Cloud 配置中心实例</a></p>
</li>
<li>
<p>在 Kubernetes 集群中执行<code>dew-devops.sh</code>脚本部署环境、创建项目，详见：<a href="#dew-devops-deploy">Dew DevOps 脚本部署</a></p>
</li>
<li>
<p>在 Gitlab上 创建项目</p>
</li>
<li>
<p>在项目中引入 Dew 的<code>parent-starter</code>依赖，详细请参考: <a href="#framework-quick-start-core-code-instructions">框架快速入门</a></p>
</li>
<li>
<p>添加用于触发 Gitlab Runner 的<code>.gitlab-ci.yml</code>配置文件，详见：<a href="#dew-gitlab-ci">Dew Gitlab CI 实现</a></p>
</li>
<li>
<p>添加 Dew Maven Plugin 支持的 Kubernetes 部署配置文件<code>.dew</code>，详见：<a href="#devops-configuration-dew">.dew 文件配置</a></p>
</li>
<li>
<p><code>合并代码</code>到需要部署的分支，触发 Gitlab Runner 进行项目部署</p>
</li>
</ol>
</div>
</div>
</div>
<div class="sect2">
<h3 id="devops示例"><a class="anchor" href="#devops示例"></a>4.6. DevOps示例</h3>
<div class="sect3">
<h4 id="devops示例_helloworld后端服务"><a class="anchor" href="#devops示例_helloworld后端服务"></a>4.6.1. DevOps示例 : Helloworld后端服务</h4>
<div class="paragraph">
<p>本示例为Dew微服务体系的组成部分，使用说明参见：http://doc.dew.ms</p>
</div>
<div class="paragraph">
<div class="title">说明</div>
<p>此示例用于演示后端服务的自动化部署到Kubernetes，完成后推送钉钉通知。</p>
</div>
<div class="listingblock">
<div class="title">/.dew配置说明</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml"># 默认通知配置，详见 Dew的通知处理模块
notifies:
  - type: DD
    args:
      # 通知的URL，可自行修改，详见 https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq
      url: https://oapi.dingtalk.com/robot/send?access_token=8ff65c48001c1981df7d326b5cac497e5ca27190d5e7ab7fe9168ad69b103455
profiles:
  # 添加测试环境
  test:
    # 指定测试环境的namespace，此namespace需要在Kubernetes及harbor中已存在
    namespace: dew-test</code></pre>
</div>
</div>
<div class="olist arabic">
<div class="title">前置准备</div>
<ol class="arabic">
<li>
<p>执行 <code>dew-devops.sh</code> 初始化项目对应的test环境（ 详见 <a href="http://doc.dew.ms/#devops-user-manual" class="bare">http://doc.dew.ms/#devops-user-manual</a> ）</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="title">手工执行</div>
<div class="content">
<pre># 参数设置 (win/unix)
SET/export dew_devops_kube_config=...
SET/export dew_devops_docker_host=...
SET/export dew_devops_docker_registry_url=...
SET/export dew_devops_docker_registry_username=...
SET/export dew_devops_docker_registry_password=...

# 执行如下maven命令(发布到test环境)
mvn -P devops dew:release -Ddew_devops_profile=test</pre>
</div>
</div>
<div class="listingblock">
<div class="title">Gitlab CI执行</div>
<div class="content">
<pre># Merge或Push代码到test分支</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="devops示例_helloworld类库"><a class="anchor" href="#devops示例_helloworld类库"></a>4.6.2. DevOps示例 : Helloworld类库</h4>
<div class="paragraph">
<p>本示例为Dew微服务体系的组成部分，使用说明参见：http://doc.dew.ms</p>
</div>
<div class="paragraph">
<div class="title">说明</div>
<p>此示例用于演示类库的自动化部署到Maven。</p>
</div>
<div class="listingblock">
<div class="title">/.dew配置说明</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">profiles:
  # 添加测试环境
  test:
    # 指定测试环境的namespace
    namespace: dew-test</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">手工执行</div>
<div class="content">
<pre># 参数设置 (win/unix)
SET/export dew_devops_it_snapshotRepository_id=&lt;私库Id，如私库需要认证则此Id对应的认证信息要添加到 settings.xml中&gt;
SET/export dew_devops_it_snapshotRepository_url=&lt;私库Url&gt;

# 执行如下maven命令(发布到test环境)
mvn -P devops dew:release -Ddew_devops_profile=test</pre>
</div>
</div>
<div class="listingblock">
<div class="title">Gitlab CI执行</div>
<div class="content">
<pre># Merge或Push代码到test分支</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="devops示例_helloworld前端"><a class="anchor" href="#devops示例_helloworld前端"></a>4.6.3. DevOps示例 : Helloworld前端</h4>
<div class="paragraph">
<p>本示例为Dew微服务体系的组成部分，使用说明参见：http://doc.dew.ms</p>
</div>
<div class="paragraph">
<div class="title">说明</div>
<p>此示例用于演示前端服务的自动化部署到Kubernetes。</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>前端需要添加一个pom.xml，指定为pom类型 <code>&lt;packaging&gt;pom&lt;/packaging&gt;</code> 即可。</p>
</div>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">/.dew配置说明</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">profiles:
  # 添加测试环境
  test:
    # 指定测试环境的namespace
    namespace: dew-test</code></pre>
</div>
</div>
<div class="olist arabic">
<div class="title">前置准备</div>
<ol class="arabic">
<li>
<p>执行 <code>dew-devops.sh</code> 初始化项目对应的test环境（详见 <a href="http://doc.dew.ms/#devops-user-manual" class="bare">http://doc.dew.ms/#devops-user-manual</a> ）</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="title">手工执行</div>
<div class="content">
<pre># 参数设置 (win/unix)
SET/export dew_devops_kube_config=...
SET/export dew_devops_docker_host=...
SET/export dew_devops_docker_registry_url=...
SET/export dew_devops_docker_registry_username=...
SET/export dew_devops_docker_registry_password=...

# 执行如下maven命令(发布到test环境)
mvn -P devops dew:release -Ddew_devops_profile=test</pre>
</div>
</div>
<div class="listingblock">
<div class="title">Gitlab CI执行</div>
<div class="content">
<pre># Merge或Push代码到test分支</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="devops示例_to_do"><a class="anchor" href="#devops示例_to_do"></a>4.6.4. DevOps示例 : To-Do</h4>
<div class="paragraph">
<p>本示例为Dew微服务体系的组成部分，使用说明参见：http://doc.dew.ms</p>
</div>
<div class="paragraph">
<div class="title">说明</div>
<p>此示例用于演示以下功能：</p>
</div>
<div class="ulist">
<ul>
<li>
<p>包含modules</p>
</li>
<li>
<p>有类库、JVM服务、前端等多种类型</p>
</li>
<li>
<p>生产环境版本重用</p>
</li>
<li>
<p>自定义.dew文件</p>
</li>
<li>
<p>自定义Docker Image</p>
</li>
<li>
<p>自动化部署到Kubernetes</p>
</li>
<li>
<p>完成后推送钉钉通知</p>
</li>
</ul>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>默认情况下Dew会视 <code>prod</code> 标识为生产环境，<code>uat</code> 为仿真环境，生产环境会重用（Docker Image重用）仿真或预发环境的最后一个版本。</p>
</div>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">/.dew配置说明</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml"># 默认通知配置，详见 Dew的通知处理模块
notifies:
  - type: DD
    args:
      # 通知的URL，可自行修改，详见 https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq
      url: https://oapi.dingtalk.com/robot/send?access_token=8ff65c48001c1981df7d326b5cac497e5ca27190d5e7ab7fe9168ad69b103455
profiles:
  # 添加uat环境
  uat:
    # 指定仿真环境的namespace
    namespace: dew-uat
  # 添加prod环境
  prod:
    # 指定生产环境的namespace
    namespace: dew-prod</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/kernel/.dew配置说明</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">docker:
  # 使用自定义的Image，默认JVM类型项目的Image为openjdk:8-alpine
  image: openjdk:8
app:
    # 资源上限设定
    containerResourcesLimits:
      memory: "1024Mi"
      cpu: "800m"</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/notifier/.dew配置说明</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">skip: true
docker:
  # 使用自定义的Image，默认JVM类型项目的Image为openjdk:8-alpine
  image: openjdk:8</code></pre>
</div>
</div>
<div class="olist arabic">
<div class="title">前置准备</div>
<ol class="arabic">
<li>
<p>执行 <code>dew-devops.sh</code> 初始化项目对应的uat和prod环境（详见 <a href="http://doc.dew.ms/#devops-user-manual" class="bare">http://doc.dew.ms/#devops-user-manual</a> ）</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="title">手工执行</div>
<div class="content">
<pre># 参数设置 (win/unix)
SET/export dew_devops_kube_config=...
SET/export dew_devops_docker_host=...
SET/export dew_devops_docker_registry_url=...
SET/export dew_devops_docker_registry_username=...
SET/export dew_devops_docker_registry_password=...
SET/export dew_devops_it_snapshotRepository_id=&lt;私库Id，如私库需要认证则此Id对应的认证信息要添加到 settings.xml中&gt;
SET/export dew_devops_it_snapshotRepository_url=&lt;私库Url&gt;

# 执行如下maven命令(发布到uat环境)
mvn -P devops dew:release -Ddew_devops_profile=uat
# 执行如下maven命令(发布到prod环境)
mvn -P devops dew:release -Ddew_devops_profile=prod</pre>
</div>
</div>
<div class="listingblock">
<div class="title">Gitlab CI执行</div>
<div class="content">
<pre># Merge或Push代码到uat分支
# Merge或Push代码到prod分支</pre>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="dew项目信息_dew_project_info"><a class="anchor" href="#dew项目信息_dew_project_info"></a>5. Dew项目信息 Dew Project Info</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="开发指南"><a class="anchor" href="#开发指南"></a>5.1. 开发指南</h3>
<div class="paragraph">
<p><code>Dew</code> 基于 Apache 2.0 license 分发，如果您要向 <code>Dew</code> 贡献代码或 Fork 为自己的分支项目请遵循许可证要求。</p>
</div>
<div class="paragraph">
<p>SCM： <a href="https://github.com/gudaoxuri/dew">https://github.com/gudaoxuri/dew</a></p>
</div>
<div class="paragraph">
<p>Issues： <a href="https://github.com/gudaoxuri/dew/issues" class="bare">https://github.com/gudaoxuri/dew/issues</a></p>
</div>
<div class="sect3">
<h4 id="代码要求"><a class="anchor" href="#代码要求"></a>5.1.1. 代码要求</h4>
<div class="paragraph">
<p>本项目使用修改版的Google CheckStyle做为代码规范，规则文件见 <code>framework/checkstyle/checkstyle.xml</code>。</p>
</div>
<div class="paragraph">
<p>代码的变更需要有有对应的单元/集成测试。</p>
</div>
<div class="paragraph">
<p>目前项目中只有<code>DevOps</code>部分用到集成测试，对应的需要修改 <code>devops/devops-test.properties</code> 为自己的支撑环境，但请不要提交此文件。</p>
</div>
<div class="listingblock">
<div class="title">/devops/devops-test.properties示例</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-properties" data-lang="properties">#
# Copyright 2019. the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
dew_devops_docker_host=tcp://10.200.131.215:2375
# Docker registry url, nullable
dew_devops_docker_registry_url=https://harbor.dew.ms/v2
# Docker registry user name, nullable
dew_devops_docker_registry_username=dew
# Docker registry password, nullable
dew_devops_docker_registry_password=Dew123456
# Kubernetes base64 configuration
dew_devops_kube_config=YXBpVmVyc2lvbjogdjEKY2x1c3RlcnM6Ci0gY2x1c3RlcjoKICAgIGNlcnRpZmljYXRlLWF1dGhvcml0eS1kYXRhOiBMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VONVJFTkRRV0pEWjBGM1NVSkJaMGxDUVVSQlRrSm5hM0ZvYTJsSE9YY3dRa0ZSYzBaQlJFRldUVkpOZDBWUldVUldVVkZFUlhkd2NtUlhTbXdLWTIwMWJHUkhWbnBOUWpSWVJGUkZOVTFFVVhoTlZFRXpUVlJCZUU0eGIxaEVWRWsxVFVSUmQwOUVRVE5OVkVGNFRqRnZkMFpVUlZSTlFrVkhRVEZWUlFwQmVFMUxZVE5XYVZwWVNuVmFXRkpzWTNwRFEwRlRTWGRFVVZsS1MyOWFTV2gyWTA1QlVVVkNRbEZCUkdkblJWQkJSRU5EUVZGdlEyZG5SVUpCU3pWbENqUkpLekJrVG5RNVFsWm1OMWhOZWxWM2FIZGtTRlZ0WmsxWGFHdGlkelU1YVVGbmFVY3lSRGcyTTNkamNWWXhhRGswTjJ4c01rVlNRM0puWlRkM1prVUtWRklyY0ZOcVJUZGlPRWhNUlRaNE5VZEhXbkkyUkhKa1kwVnhhRFZSYzNoeUwyODVOVGxrYVNzeldYTmFNR3AwYVRSalFVNURjMGR2T0RkaFZpOUNOQXAzUmpBMmEyOXJVVU5yYlZSYVNXbG1kRWQwYW5CWlJWUkljV0phVlhwMlpYTnNRamRhY0daeWVXY3ZhWHBWVjI0M0x6VXhkVTVhZEdaclZtSkRhR0pCQ2psS1ozSnlXVFJqYTBZck1XdG5VVmxrTjFKMlRsRnVkMDQwTm5WbGJsUklhVlJaYWpONGVYVnVkbU5GYTNZd1RVNU1WV2haUXpOQ2JWcDNlVmxtTWxZS1IwaHJZbWt4VEZWa01WSlNSWFJVUTNKdWIydExPRUZ3UVV0NE4yaGpaVFpETm5OYVEwWTNWVEpvYXpWSk1qTlVUa2xJUkZGb1dGUkZka3BhTlZCbFpncFNNMDlWY0Zaak0wVkliREJPVDFGMFZ6Tk5RMEYzUlVGQllVMXFUVU5GZDBSbldVUldVakJRUVZGSUwwSkJVVVJCWjB0clRVRTRSMEV4VldSRmQwVkNDaTkzVVVaTlFVMUNRV1k0ZDBSUldVcExiMXBKYUhaalRrRlJSVXhDVVVGRVoyZEZRa0ZKZVdab1JqTmljMkZIWlRSeGQwMVdhRE14VjNCT1NTOU9URUVLZEZCc01VWkllbWxNYW5BcldVMW9MMjlyZDFKcmQzaERUbU5QWVhwRGVVUnlSbkozYVhkbE0ydG9USEExYVZwak5WTnVRMlJWUjBobVNsTjZPVEJwZWdwamNDdEViRXhQWnpKWGNUbFFObk5qTkZCV2JsaEhNRTVZUjBZMU1sSmtkRWw2Y205UE5ESkVVVWRDVTBSUGFVbHlTM2RpY25Bdk1HUTRLMWs1ZHl0WkNrSnVXVGxZZEVWNlNtOVdRMkp0YTFock5UTXlTa04xU0ZaSlVtOWpURGxUYjI1UVpIVjRNWFo0YjFJcmNuQTJRWE5VTkN0cloxRTFXbkJaVVZreVQzVUthVVJMTnpGWlFrbzBjbWxSZDBNNWJWRnJaV2RSYXpsdWFFUk9ORWhxYzIxMWJIQmFiemRJU0ZCTVZtVXZNa3BEZEhaRFUzUnVkVTFFYjNOQ1ZWRnJNZ292Ykd0b05uZzNkRlF2YWpKSU4wRldXalZMTlc5NE5FMHhPVFZ4ZEhrMFlsTmFjbmhoTWpVek5FTnpSblpJTTA1M1REZE1hMGhTYW5Sc2N6MEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0KICAgIHNlcnZlcjogaHR0cHM6Ly8xMC4yMDAuMTMxLjE4OjY0NDMKICBuYW1lOiBrdWJlcm5ldGVzCmNvbnRleHRzOgotIGNvbnRleHQ6CiAgICBjbHVzdGVyOiBrdWJlcm5ldGVzCiAgICB1c2VyOiBrdWJlcm5ldGVzLWFkbWluCiAgbmFtZToga3ViZXJuZXRlcy1hZG1pbkBrdWJlcm5ldGVzCmN1cnJlbnQtY29udGV4dDoga3ViZXJuZXRlcy1hZG1pbkBrdWJlcm5ldGVzCmtpbmQ6IENvbmZpZwpwcmVmZXJlbmNlczoge30KdXNlcnM6Ci0gbmFtZToga3ViZXJuZXRlcy1hZG1pbgogIHVzZXI6CiAgICBjbGllbnQtY2VydGlmaWNhdGUtZGF0YTogTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTTRha05EUVdSeFowRjNTVUpCWjBsSlJWUlZjbE5SV21kblowRjNSRkZaU2t0dldrbG9kbU5PUVZGRlRFSlJRWGRHVkVWVVRVSkZSMEV4VlVVS1FYaE5TMkV6Vm1sYVdFcDFXbGhTYkdONlFXVkdkekI0VDFSQk1FMVVSWGRPZWtWM1RWUmtZVVozTUhsTlJFRXdUVlJCZDA1NlJYZE5ha1poVFVSUmVBcEdla0ZXUW1kT1ZrSkJiMVJFYms0MVl6TlNiR0pVY0hSWldFNHdXbGhLZWsxU2EzZEdkMWxFVmxGUlJFVjRRbkprVjBwc1kyMDFiR1JIVm5wTVYwWnJDbUpYYkhWTlNVbENTV3BCVGtKbmEzRm9hMmxIT1hjd1FrRlJSVVpCUVU5RFFWRTRRVTFKU1VKRFowdERRVkZGUVhSdmFXOHdjbWxTU2k5QlkzQXhlVE1LUjBKbVkxaGhRbWhMTlRWek1EWk1ibEI2TmpCWGVEQnlkalZ6SzNWeVdVaFliWFYyU2pWVGNrVlpRbEpIWWtWdlRIcDVVemhYVW1KU01HdDFjbEJOUlFvMVpWUlpUR012UnpaeGNuVnVka1p4UjNSak4xaFJSMGN6VkVzdlJUSnNLMkkxZVdJeGEwTnliM2htTTNOelVFeFVVbVZDT0hFd1NrWm5iRmRpZEdjeUNsZElWVFJhUlZrNE1sbzVObkVyWkV4clZHaFJNRUV4YlZWMlUxRjZWMFlyU2xOaVNYbHNkVTV4VWxBMGExaHdZME16U1hKQlozcEdPWEZITkV0R1V6Z0tkM2xuUm5sRU9VUlJja3cyWkRGcE5qaDFVazR6YW10bFZYTTBNMHRDTlc4cldGcEhka05KY2tsR1owaDNXRUZhVkRkVE1ub3ZaRVJRZG5vcmFVaFdVQXBZYzFaUlIweEVkRGx4YWtwR2JYRk1WazlRZVVGelFuWlhWM0Z6VFZaTFpWSXJWbGhYYkRoSll6bG9iMFZWVkdOcmEyMXhRV0YzWkdNMlEzcEplbFJ5Q21FelNqRTRVVWxFUVZGQlFtOTVZM2RLVkVGUFFtZE9Wa2hST0VKQlpqaEZRa0ZOUTBKaFFYZEZkMWxFVmxJd2JFSkJkM2REWjFsSlMzZFpRa0pSVlVnS1FYZEpkMFJSV1VwTGIxcEphSFpqVGtGUlJVeENVVUZFWjJkRlFrRkRSVmRUYTB3MFkwRmFhekJsVnpScVMzTlhhVEV6YTAxSmJ5ODNMM3BZZG1aRWFBcGpTR05YYVdONlRqUndNRGs1T0RoVmVtbHdVak54YUZCMmVHaHhXR0pwTlRKS09WVlJOVkFyVUUxS05IRjJOamRSU1VGM1pHMXhlRXRzZFVORldqWk1DazVoYmtWRGNtZDVaek5QZW1kQ1dtaElUa3N4YUhob2VtdzBMME54YTBKTmNGbEJNSEprWWpKVlpUTlhiV05wV1hkc1EyUk9Ramw2ZVhKV01VUXZVa2tLZW5aTFYwUnJUMU5xV2pjMFVGWlpXbWh5VVdkWFJWVllPVWRGYlVsSlZXcFFZbXhsYTNKV1kyOXVTbmxpT1ZwV2VqZDNNbFZNVUVkTFkxcG1iMHBMYkFwWUt5OWhhVk5SYWs0NWRFazRRelEyYUd0aWFtTlFkalJwVHpJMmNtcFZSRTB2VDJsRWFuVlBLMnRFTjFaTlFqZHpWMjFTVFZkUWVGbFhTWE5rZG5wNUNtRk5Semd3VnpsMk9FMUpSbFJ4TjBsVVNVTTJjelowVld0aWFsUjVNMlZwVUhSM1ozUlFLMU5aZG1GRFVWRndiVFl6WnowS0xTMHRMUzFGVGtRZ1EwVlNWRWxHU1VOQlZFVXRMUzB0TFFvPQogICAgY2xpZW50LWtleS1kYXRhOiBMUzB0TFMxQ1JVZEpUaUJTVTBFZ1VGSkpWa0ZVUlNCTFJWa3RMUzB0TFFwTlNVbEZiMmRKUWtGQlMwTkJVVVZCZEc5cGJ6QnlhVkpLTDBGamNERjVNMGRDWm1OWVlVSm9TelUxY3pBMlRHNVFlall3VjNnd2NuWTFjeXQxY2xsSUNsaHRkWFpLTlZOeVJWbENVa2RpUlc5TWVubFRPRmRTWWxJd2EzVnlVRTFGTldWVVdVeGpMMGMyY1hKMWJuWkdjVWQwWXpkWVVVZEhNMVJMTDBVeWJDc0tZalY1WWpGclEzSnZlR1l6YzNOUVRGUlNaVUk0Y1RCS1JtZHNWMkowWnpKWFNGVTBXa1ZaT0RKYU9UWnhLMlJNYTFSb1VUQkJNVzFWZGxOUmVsZEdLd3BLVTJKSmVXeDFUbkZTVURScldIQmpRek5KY2tGbmVrWTVjVWMwUzBaVE9IZDVaMFo1UkRsRVVYSk1ObVF4YVRZNGRWSk9NMnByWlZWek5ETkxRalZ2Q2l0WVdrZDJRMGx5U1VablNIZFlRVnBVTjFNeWVpOWtSRkIyZWl0cFNGWlFXSE5XVVVkTVJIUTVjV3BLUm0xeFRGWlBVSGxCYzBKMlYxZHhjMDFXUzJVS1VpdFdXRmRzT0Vsak9XaHZSVlZVWTJ0cmJYRkJZWGRrWXpaRGVrbDZWSEpoTTBveE9GRkpSRUZSUVVKQmIwbENRVVV2SzJGbldWaEVjalI1T0RCMlFncFVVbWhKT1V4RmRscFJXa3B2Y1ZoS1NWcEVUU3RTZEhCSFJrRmlVMEV4Y0ZWeFpHeG9PV2hRZUdaNVoyZEhjRVJCYW5CYVZVbG9jbXMzYTFVNFJtbHdDa1V2T1hkSUwwWk1kVzlCUkUxNGIwTTRTalJqY25jMVpXRmxNSE51VTBwNmRrRTFUREIwZFRsalluSkRLMVJIYkhKeldERlNSMVJIZDJ4SFpTc3ZjMDRLTVVKMFIwYzVWMDlHYjB4Rk5tMTZSbkUwT1ZRek9EWmtORXhpTDJFMmRIbGFRV1pOZEVGYWNFaGhjRnBHZEVwYVZXOTJlRXRFZVVkWE56Rk9UMElyWWdwdWN6WlRaV2hRV1VZMU0wbGFXRGswYWk5NVJFeDNXRTVYT0ZNMmFGbEdORVpwY1RReVMyRkxlU3RFZVhKNVFrMTRVVzV4UzJaU1RsaDRRMlE0ZEhkeENuSjZWRGQwUldaQk1qVlljRllyU0hOemNVWjJVMVZEZDA1eFptbGxZbFprV25oSFdVc3dlbU5ZYnl0WWFuWlVVRlp3VTBkMlVrbHFaRlpOZFN0elNEQUtSak55VkZKNlZVTm5XVVZCTWpZeE5sTnRjbXhGWW1OTFIzbFFWRFJOUVVOVldFNVRabFJHU1RBMWJGWnRiVmRXU1RaTlRXRXlVSHBoWVRNeU4xcDFPUXBESzBwQlFYcFhTQ3RYVmpaaVZEQjVXbnBCU1Vwa05rY3JZbUZCZVhGMFVHaEdka1p6Y20xNWRXTk9lV2xzZURobVl6UkpVMHRLU3l0a2FrcHFOR0ZLQ2xKUlJsRk5UbkZ2VlhSblZXZHFVbE54WkRKNVkzcGFWRGcyTlM5VGJtWktRVVZ6U213dk9VdDBhSFpCZGpWWWIwdHBPWFJ1Tm5ORFoxbEZRVEZNWWpBS0syeE1kbWgwZVRWQmMwTkZZMGRNWTNKSlZTOWxTMUYyVHk5alJWbDNZMllyTjJ0aFZHZDJVMHN6Y0ZaVFUzcDZXWGhSUVVwa1NEYzNRWHBsWlVOT1RBcEphVlJoY1M5U2QweHlTalJrYlVkeVRYWmFhVVJZZVVWNFlUZDRjR1U0VldkQlpEQjFUV1pIYlRGbFIwdHBUelp6VmpKWFlrTkljME5EVm5sdmQyaHZDbUpLVGxwbVMwOUxObHBvVkZwaVUwZE9kSFJqVVdkaVRDdG1SM2t3U2s5amJqVmFZbXhPVFVObldVRTRaMjVvWnpkNmFsSjJSakZ2VWtkSmExVnFPVklLVDBSaVRXcFVja2RNYTBwTFIxSnJVR2tyV0hJemMyYzFiamxYV1dSWlIwcFBTRlI0WWxac1lUbFFlbGxCYW1SVUwxRk5RbWt4TkdGbmJ6ZFFVa2xxVHdweFNXazBVbXBhV1haUFJtTkJNRmN6VlZnd2RYQkxWMHRXU2tOUmJrUndUR0UyVlhaRU9IVXhjR2hrWjBab1JpOUJURmxoZW1sbmNIZDZUVVY2UTBoMENteHlNalY0U0ZKTU0xUkZiV3hMUVhsdWIxaFFjVkZMUW1kQ2JsWXZkblJKYURNNVFqTTVZazUxTUdoTVQySktZVzVPVEhsWWFYQlRXR3cwU1hnMFIzb0taMlJhYnpaVFdVOTJZakJrYTBwS1FVazJVakJXVEhwbE9EQmFNRkYyVUhGMFoxTmpjeXRPTkVka2JVaEZNWGRzVVU1UFUyaERNMlZwWTNNeGRIRXJNZ3BQUWpoYVdrczBTbVV5YjNrelRVbGxUVGszSzFnNFV6bG9ObUl4Y0c1c1NtcEdlblpKUkdkMlNVUkpRMDE1YzBkcFlsbGlWRlJ6VjJWRWJVNU9SVVJPQ2padFIzaEJiMGRCUVc5TVZEUmpOMGRJYkRaWlV6SjVRazlJWTJ4UU9FTTNZMnMwVDBFNE0xcFFRbmhGVkVzeGVua3hSblphVlcxb1ZGcDNlbmRJUlVFS1JEWTFUbWhVTW1SWlUwcFNXV3hKZFd0S1ptcEpTV0ZXWjNobVZHdGtTa0lyWlhOYWVtcHNXVkJFYTNWbVEzRmphWEY2UkZSNlptSlZSa29yYWpGMVpBcGhNbU00UlZCV2QyWTJRMWxLV2taYU1FZEpMMkp6VVN0Vk5XVnZZemN3ZW1FeVNtMUlhRVl5YTNocmRIUjZSME0zVTNNOUNpMHRMUzB0UlU1RUlGSlRRU0JRVWtsV1FWUkZJRXRGV1MwdExTMHRDZz09Cg==
# Repository id, must be filled out in version replacement mode
# If the repository needs authentication, the authentication information corresponding to this Id should be added to settings.xml
dew_devops_it_repository_id=dew-releases
# Snapshot  repository url, must be filled out in version replacement mode
dew_devops_it_repository_url=http://xx.xx.xx/nexus/content/repositories/releases/
# Snapshot repository id, must be filled out in version replacement mode
# If the repository needs authentication, the authentication information corresponding to this Id should be added to settings.xml
dew_devops_it_snapshotRepository_id=dew-snapshots
# Snapshot  repository url, must be filled out in version replacement mode
dew_devops_it_snapshotRepository_url=http://xx.xx.xx/nexus/content/repositories/snapshots/</code></pre>
</div>
</div>
<div class="paragraph">
<p>提交前请运行 <code>mvn clean test -P qa</code>，确认代码通过CheckStyle检查和测试。</p>
</div>
<div class="paragraph">
<p>发起 <code>Pull Request</code> 后会调用 <code>travis-ci</code> 和 <code>codacy</code> 进行自动化代码检查，如有问题不会执行 <code>Merge</code> 。</p>
</div>
</div>
<div class="sect3">
<h4 id="环境要求"><a class="anchor" href="#环境要求"></a>5.1.2. 环境要求</h4>
<div class="paragraph">
<p><code>开发</code> 环境没有特别要求，如果需要服务间调用可使用 <code>&lt;服务名&gt;.ribbon.listOfServers=&lt;host:port&gt;</code> 的方式，详见 <code>examples/tracing-invokeX-example</code> 。</p>
</div>
<div class="paragraph">
<p><code>调试</code> 环境部分模块需要Kubernetes、Docker等服务的支持。</p>
</div>
<table class="tableblock frame-all grid-all spread">
<colgroup>
<col style="width: 33.3333%;">
<col style="width: 33.3333%;">
<col style="width: 33.3334%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">环境</th>
<th class="tableblock halign-left valign-top">模块</th>
<th class="tableblock halign-left valign-top">备注</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">RabbitMQ</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">cluster-rabbit</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">测试RabbitMQ</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Kubernetes Docker</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">tracing-invokeX-example</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">测试服务间调用</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Kubernetes Docker</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">dew-maven-plugin</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">DevOps插件</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Kubernetes Docker</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">helloworld-backend
                     helloworld-frontend
                     helloworld-library
                     todo</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">测试自动化部署</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Opentracing Jaeger</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">todo</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">测试追踪日志</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Fluentd</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">todo</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">测试日志采集</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Prometheus Grafana</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">todo</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">测试Metrics</p></td>
</tr>
</tbody>
</table>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>如果已按 <a href="#devops-env-install">测试、生产环境安装配置</a> 完成了测试环境的安装配置，
那么可部分使用这一环境进行调试，
否则请按以下流程完成调试环境的安装配置。</p>
</div>
</td>
</tr>
</table>
</div>
<div class="sect4">
<h5 id="调试环境安装配置"><a class="anchor" href="#调试环境安装配置"></a>调试环境安装配置</h5>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
<div class="paragraph">
<p>Kubernetes、Docker环境要求使用代理。</p>
</div>
<div class="paragraph">
<p>详见 <a href="#proxies">代理选择与设置</a></p>
</div>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">Kubernetes安装</div>
<div class="content">
<pre># 使用minikube

# 各版本的安装见此
# https://kubernetes.io/docs/tasks/tools/install-minikube/

# 代理设置见此
# https://github.com/kubernetes/minikube/blob/master/docs/http_proxy.md
# !!!特别说明!!!
# 以上说明有问题，export HTTP_PROXY或set HTTP_PROXY只会设置到宿主环境，但在vm环境不会带入，
# 故代理的地址要直接写到 minikube start 的启动参数中
# 代理地址不能写成127.0.0.1，vm中无法解析
# windows 10 示例
minikube start \
    --registry-mirror=https://registry.docker-cn.com \
    --vm-driver=hyperv --hyperv-virtual-switch=public --memory=4096 \
    --docker-env=HTTP_PROXY=http://10.200.20.147:1080 \
    --docker-env=HTTPS_PROXY=http://10.200.20.147:1080 \
    --docker-env=NO_PROXY=localhost,127.0.0.1,10.96.0.0/12,192.168.99.1/24
# 查询kubernetes IP
minikube ip
# 安装 dashboard
minikube dashboard</pre>
</div>
</div>
<div class="listingblock">
<div class="title">RabbitMQ安装</div>
<div class="content">
<pre>minikube ssh
docker run -d --hostname rabbit --name rabbit \
    --restart=always \
    -e RABBITMQ_DEFAULT_USER=dew \
    -e RABBITMQ_DEFAULT_PASS=dew123456 \
    -p 15672:15672 \
    -p 25672:25672\
    -p 5671:5671 \
    -p 5672:5672 \
    -p 4369:4369 \
    rabbitmq:management

# 访问页面
# Visit: http://&lt;minikube ip&gt;:15672</pre>
</div>
</div>
<div class="listingblock">
<div class="title">Opentracing Jaeger安装</div>
<div class="content">
<pre>minikube ssh
docker run -d --name jaegertracing \
    --restart=always \
    -p 5775:5775/udp \
    -p 6831:6831/udp \
    -p 6832:6832/udp \
    -p 5778:5778 \
    -p 16686:16686 \
    -p 14268:14268 \
    -p 9411:9411 \
    jaegertracing/all-in-one:latest

# 访问页面
# Visit: http://&lt;minikube ip&gt;:16686</pre>
</div>
</div>
<div class="listingblock">
<div class="title">Prometheus、Grafana安装</div>
<div class="content">
<pre>minikube ssh

# 参考 https://prometheus.io/docs/prometheus/latest/getting_started/#configuring-prometheus-to-monitor-itself
# 示例监控的是 web-example 工程，对应的配置: framework/examples/web-example/src/main/resources/application.yml
# prometheus使用pull的方式，需要配置应用运行服务器的IP
cat &gt;&gt;./prometheus.yml &lt;&lt;EOF
scrape_configs:
  - job_name: 'dew-web-example-actuator'
    metrics_path: '/actuator/prometheus'
    scrape_interval: 5s
    static_configs:
    - targets: ['10.200.20.147:80']
EOF
docker run -d --name prometheus \
    --restart=always \
    -p 9090:9090 \
    -v $PWD/prometheus.yml:/etc/prometheus/prometheus.yml \
    prom/prometheus

# 访问页面
# Visit: http://&lt;minikube ip&gt;:9090

docker run -d --name=grafana \
    --restart=always \
    -p 3000:3000 \
    -e GF_SECURITY_ADMIN_USER=dew \
    -e GF_SECURITY_ADMIN_PASSWORD=dew123456 \
    grafana/grafana

# 访问页面
# Visit: http://&lt;minikube ip&gt;:3000</pre>
</div>
</div>
</div>
<div class="sect4">
<h5 id="扩展尝试_可选"><a class="anchor" href="#扩展尝试_可选"></a>扩展尝试（可选）</h5>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
Windows10请开启 <code>WSL</code>，不支持WSL的Windows请安装 <code>cygwin</code>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">Windows10 WSL设置</div>
<div class="content">
<pre># 开启WSL，使用linux环境操作kubernetes，以WSL ubuntu为例

# windows下的用户名
export WIN_USERNAME=i
# 为kubernetes IP，使用 ``minikube ip`` 查询
export KUBE_IP=10.200.20.220
# 为linux添加代理
export HTTP_PROXY=http://127.0.0.1:1080
export HTTPS_PROXY=http://127.0.0.1:1080
export NO_PROXY=localhost,127.0.0.1,10.96.0.0/12,192.168.99.1/24,$KUBE_IP

# 安装kubectl
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl \
&amp;&amp; chmod +x ./kubectl \
&amp;&amp; sudo mv ./kubectl /usr/local/bin/kubectl

# 配置kubernetes集群信息
kubectl config set-cluster minikube \
    --server=https://$KUBE_IP:8443 \
    --certificate-authority=/mnt/c/Users/$WIN_USERNAME/.minikube/ca.crt
kubectl config set-credentials minikube \
    --client-certificate=/mnt/c/Users/$WIN_USERNAME/.minikube/client.crt \
    --client-key=/mnt/c/Users/$WIN_USERNAME/.minikube/client.key
kubectl config set-context minikube \
    --cluster=minikube \
    --user=minikube

kubectl config use-context minikube
kubectl get node</pre>
</div>
</div>
<div class="listingblock">
<div class="title">Helm安装</div>
<div class="content">
<pre>curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash
helm init
# 查询tiller是否部署完成
kubectl -n kube-system get po | grep tiller
helm list</pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="版本"><a class="anchor" href="#版本"></a>5.2. 版本</h3>
<div class="sect3">
<h4 id="通道说明"><a class="anchor" href="#通道说明"></a>5.2.1. 通道说明</h4>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
<div class="paragraph">
<p><code>Dew</code>有多个版本通道，使用时请谨慎选择：</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p><code>GA</code> General Availability，正式版本，通过内部测试没有已知错误并且经过生产验证，生产环境首选！</p>
</li>
<li>
<p><code>RC</code> Release Candidate，发行候选版本，通过内部测试没有已知错误，可用于生产环境。</p>
</li>
<li>
<p><code>Beta</code> 公开测试版本，没有已知的Major类型Bug，但允许存在个别minor类型Bugs，生产环境使用需要谨慎评估！</p>
</li>
<li>
<p><code>Alpha</code> 内部测试版本，很早期的测试版本，未经过内部测试，可能存在较多Bugs，此版本类似技术预览版（Technical Preview），切 <strong>不可</strong> 用于生产环境！</p>
</li>
<li>
<p><code>SNAPSHOT</code> 快照版本，类似Nightly版本，更新频繁此不保证质量，切 <strong>不可</strong> 用于生产环境！</p>
</li>
</ol>
</div>
</td>
</tr>
</table>
</div>
</div>
<div class="sect3">
<h4 id="发行记录"><a class="anchor" href="#发行记录"></a>5.2.2. 发行记录</h4>
<div class="sect4">
<h5 id="2_0_0_rc_准备中"><a class="anchor" href="#2_0_0_rc_准备中"></a>2.0.0-RC(准备中)</h5>
<div class="ulist">
<div class="title">Features</div>
<ul>
<li>
<p>升级到Spring Boot 2.x</p>
</li>
<li>
<p>实现兼容Spring Cloud与Istio的部署、监控架构</p>
</li>
<li>
<p>引入DevOps流程</p>
</li>
<li>
<p>独立通知功能到<code>notification</code></p>
</li>
<li>
<p>支持kafka、MQTT的MQ实现</p>
</li>
<li>
<p>支持多个Redis连接 <a href="#framework-user-manual-cluster-redis-mutli-connection">多实例支持</a></p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Improvement</div>
<ul>
<li>
<p>核心模块通过<code>checkstyle</code>检查</p>
</li>
<li>
<p><code>dew-jdbc</code> 被移除，请直接使用其它数据库管理工具</p>
</li>
<li>
<p><code>cluster-spi-eureka</code> 被移除，请用 <code>cluster-spi-redis</code> 代替其集群选举功能</p>
</li>
<li>
<p>权限认证Token处理优化 <a href="#framework-user-manual-auth">权限认证</a></p>
</li>
</ul>
</div>
</div>
<div class="sect4">
<h5 id="1_5_1_rc"><a class="anchor" href="#1_5_1_rc"></a>1.5.1-RC</h5>
<div class="ulist">
<div class="title">Improvement</div>
<ul>
<li>
<p>升级 <code>dew-common</code> 允许请求与响应编码不同</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Fixed</div>
<ul>
<li>
<p>修复 <code>Dew.Util.getRealIP</code> 错误</p>
</li>
</ul>
</div>
</div>
<div class="sect4">
<h5 id="1_5_0_rc"><a class="anchor" href="#1_5_0_rc"></a>1.5.0-RC</h5>
<div class="ulist">
<div class="title">Features</div>
<ul>
<li>
<p>使用小泰科技Fork版本做为开源版本</p>
</li>
<li>
<p>添加领导者选举的Redis实现</p>
</li>
<li>
<p>添加消息通知（钉钉或邮件）</p>
</li>
<li>
<p>添加生成系统级（多服务）统一离线文档功能</p>
</li>
<li>
<p>添加MQ消费的HA功能</p>
</li>
<li>
<p>默认使用micrometer做为指标采集工具</p>
</li>
<li>
<p>添加对Scala的支持</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Improvement</div>
<ul>
<li>
<p>分布式锁中删除lock、lockWithFun操作</p>
</li>
<li>
<p>分布式锁由可重入改为不可重入</p>
</li>
<li>
<p>redis增加hash incr操作 和 hash decr操作</p>
</li>
<li>
<p>增加swagger-bootstrap-ui，优化swaggerUI的显示</p>
</li>
<li>
<p>spring-boot升级至1.5.13.RELEASE版本</p>
</li>
<li>
<p>spring-cloud升级到Edgware.SR4版本</p>
</li>
<li>
<p>dew-common升级到1.4.7版本</p>
</li>
<li>
<p>boot-starter默认启用HTTP服务</p>
</li>
<li>
<p>移除ShardingJDBC的内容</p>
</li>
<li>
<p>移除服务脚手架功能</p>
</li>
<li>
<p>移除mybatis-starter模块</p>
</li>
<li>
<p>暂时移除Dew JDBC模块</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Fixed</div>
<ul>
<li>
<p>修复指标采集内存溢出问题</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">迁移指南（从1.3.4-RC到此版本）</div>
<ul>
<li>
<p>配置变更： 拆分<code>dew.cluster.dist</code> 为 <code>dew.cluster.lock</code>和<code>dew.cluster.map</code></p>
</li>
<li>
<p>配置变更： <code>dew.cluster.election.config.election-period-sec</code> to <code>dew.cluster.config.election-period-sec</code></p>
</li>
<li>
<p>功能变更： 领导者选举、分布式锁、分布式Map的实例化方式由 <code>dew.cluster.election/lock/map</code> 修改成 <code>dew.cluster.election/lock/map.instance(&#8230;&#8203;)</code></p>
</li>
<li>
<p>功能变更： 领导者选举<code>isLeader</code>接口需要等待选举产生后再返回（之前逻辑是每次启动时会设置成false再执行选举）</p>
</li>
<li>
<p>功能变更： 相同<code>Dew.Info.instance</code>的实例在选举过期周期内重启任能保持原先状态</p>
</li>
<li>
<p>功能变更： 移除服务脚手架，需要手工添加需要的接口服务</p>
</li>
<li>
<p>功能变更： 移除mybatis-starter模块，请使用mybatis官方方案</p>
</li>
<li>
<p>功能变更： swagger-ui.html 变更成 doc.html</p>
</li>
<li>
<p>功能变更： <code>Dew.Info.instance</code>由<code>UUID</code>修改成<code>服务名@Profile@IP:端口</code></p>
</li>
<li>
<p>功能变更： 升级后的Tomcat版本不支持Host中带有'_'这种非规范符号</p>
</li>
</ul>
</div>
</div>
<div class="sect4">
<h5 id="1_3_4_rc"><a class="anchor" href="#1_3_4_rc"></a>1.3.4-RC</h5>
<div class="ulist">
<div class="title">Features</div>
<ul>
<li>
<p>rabbitmq 增加topic exchange</p>
</li>
</ul>
</div>
</div>
<div class="sect4">
<h5 id="1_3_2_rc"><a class="anchor" href="#1_3_2_rc"></a>1.3.2-RC</h5>
<div class="ulist">
<div class="title">Features</div>
<ul>
<li>
<p>去掉logback-es依赖，使用logstash从日志文件进行采集</p>
</li>
</ul>
</div>
</div>
<div class="sect4">
<h5 id="1_3_1_rc"><a class="anchor" href="#1_3_1_rc"></a>1.3.1-RC</h5>
<div class="ulist">
<div class="title">Fixed</div>
<ul>
<li>
<p>#93 修复mybatis-starter对于sharding-jdbc数据源的强制加载</p>
</li>
</ul>
</div>
</div>
<div class="sect4">
<h5 id="1_3_0_rc"><a class="anchor" href="#1_3_0_rc"></a>1.3.0-RC</h5>
<div class="ulist">
<div class="title">Features</div>
<ul>
<li>
<p>#87 局部使用sharding-jdbc，mybatis实现，增加mybatis-starter模块</p>
</li>
<li>
<p>#89 支持配置提示</p>
</li>
<li>
<p>#91 Dew实例加载机制优化</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Improvement</div>
<ul>
<li>
<p>#82 metrics指标增加线程、内存、cpu、磁盘等统计</p>
</li>
<li>
<p>#86 ErrorController增加zuul日志追踪支持</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Fixed</div>
<ul>
<li>
<p>#92 修复logback-elasticsearch日志压力过大时导致的内存泄漏</p>
</li>
</ul>
</div>
<div class="olist arabic">
<div class="title">升级指南</div>
<ol class="arabic">
<li>
<p>修改pom.xml中dew版本号为1.3.0-RC</p>
</li>
<li>
<p>1.3.0-RC版本中已移除启动类配置，直接用<code>@SpringBootApplication</code>或<code>@SpringCloudApplication</code></p>
</li>
<li>
<p>启动类需要的注解不要忘记自行添加，如<code>@EnableTransactionManagement</code>、<code>@EnableScheduling</code></p>
</li>
<li>
<p>新增的mybatis-starter模块，详见使用说明</p>
</li>
</ol>
</div>
</div>
<div class="sect4">
<h5 id="1_2_2_rc"><a class="anchor" href="#1_2_2_rc"></a>1.2.2-RC</h5>
<div class="ulist">
<div class="title">Fixed</div>
<ul>
<li>
<p>#45 邮件通知修正</p>
</li>
<li>
<p>#85 日志配置优化</p>
</li>
</ul>
</div>
</div>
<div class="sect4">
<h5 id="1_2_1_rc"><a class="anchor" href="#1_2_1_rc"></a>1.2.1-RC</h5>
<div class="ulist">
<div class="title">Fixed</div>
<ul>
<li>
<p>#38 RabbitMQ消息未设置持久化</p>
</li>
<li>
<p>使用 统一响应——协议无关 类型时，降级HTTP状态码改为500</p>
</li>
</ul>
</div>
</div>
<div class="sect4">
<h5 id="1_2_0_rc"><a class="anchor" href="#1_2_0_rc"></a>1.2.0-RC</h5>
<div class="ulist">
<div class="title">Features</div>
<ul>
<li>
<p>#75 添加幂等处理功能， #77 可选策略类型Bloom Filter尚在开发中</p>
</li>
<li>
<p>#72 实现针对服务整体及每个接口的TPS、最大/平均/90%响应时间Metrics统计</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Improvement</div>
<ul>
<li>
<p>#68 支持自定义离线文档文件名</p>
</li>
<li>
<p>#70 更友好地获取本机Host</p>
</li>
<li>
<p>#76 cluster.cache 支持更多类型的操作</p>
</li>
<li>
<p>#53 统一响应——协议无关 降级由 <code>1000</code> 改成 <code>555</code> 以提升兼容性</p>
</li>
<li>
<p>#79 增加是否启用默认文档配置</p>
</li>
<li>
<p>#80 增加注解启用Dew功能</p>
</li>
<li>
<p>Swagger文档去除全局token参数</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Fixed</div>
<ul>
<li>
<p>#43 swagger2markup-maven-plugin 在使用 spring.content-path 无效</p>
</li>
</ul>
</div>
<div class="olist arabic">
<div class="title">从 <code>1.1.0-RC</code> 迁移到 <code>1.2.0-RC</code></div>
<ol class="arabic">
<li>
<p>使用 <code>统一响应——协议无关</code> 类型时，UI端由原来只需要获取200状态下的数据改成需要获取 200 和 555 状态下的数据，两者对UI端没有区别。( @See <a href="https://rep.360taihe.com/csp/dew-framework/issues/53" class="bare">https://rep.360taihe.com/csp/dew-framework/issues/53</a> )</p>
</li>
</ol>
</div>
</div>
<div class="sect4">
<h5 id="1_1_0_rc"><a class="anchor" href="#1_1_0_rc"></a>1.1.0-RC</h5>
<div class="ulist">
<div class="title">Features</div>
<ul>
<li>
<p>[功能] #45 支持服务调用（ <code>Hystrix</code> ）异常邮件通知</p>
</li>
<li>
<p>[功能] #51 适配新版 <code>用户权限中心</code> SDK</p>
</li>
<li>
<p>[功能] #59 #49 #15 统一日志规范，适配 <code>sleuth</code> 日志到 <code>ES</code></p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Improvement</div>
<ul>
<li>
<p>[优化] #53 统一响应——协议无关 类型的http返回码由统一的200改成 <code>200</code> 或 <code>1000</code> ，前者表示操作成功或不需要降级的错误，后者表示需要做降级（Hystrix fallback）的错误</p>
</li>
<li>
<p>[优化] #50 <code>Dew JDBC</code> 更好地支持没有 <code>Entity</code> 注解的对象</p>
</li>
<li>
<p>[优化] #52 对于java8时间，url参数转换支持String转LocalDateTime,LocalDate、LocalTime,long转LocalDateTime(但json数据不支持)，long转Instant</p>
</li>
<li>
<p>[优化] #55 #58 其它一些优化</p>
</li>
</ul>
</div>
<div class="olist arabic">
<div class="title">从 <code>1.1.0-beta1</code> 迁移到 <code>1.1.0-RC</code></div>
<ol class="arabic">
<li>
<p>使用 <code>统一响应——协议无关</code> 类型时，UI端由原来只需要获取200状态下的数据改成需要获取 200 和 1000 状态下的数据，两者对UI端没有区别。( @See <a href="https://rep.360taihe.com/csp/dew-framework/issues/53" class="bare">https://rep.360taihe.com/csp/dew-framework/issues/53</a> )</p>
</li>
</ol>
</div>
</div>
<div class="sect4">
<h5 id="1_1_0_beta1"><a class="anchor" href="#1_1_0_beta1"></a>1.1.0-beta1</h5>
<div class="ulist">
<div class="title">Features</div>
<ul>
<li>
<p>[功能] #19 支持局部 <code>ShardingJDBC</code>(由于ShardingJDBC 2.0还未RC，测试发现存在较多问题，此功能需要等待官方RC)</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Improvement</div>
<ul>
<li>
<p>[优化] 支持Java8时间处理</p>
</li>
<li>
<p>[优化] #34 模块Spring化，<code>boot-core</code> 更名为 <code>boot-starter</code> , <code>cloud-core</code> 更名为 <code>cloud-starter</code></p>
</li>
<li>
<p>[优化] #40 <code>Dew JDBC</code> 独立成 <code>jdbc-starter</code> , 确保核心模块 <code>boot-starter</code> 更轻量</p>
</li>
<li>
<p>[优化] <code>Dew JDBC</code> 性能优化</p>
</li>
<li>
<p>[文档] #47 添加性能调优章节</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Fixed</div>
<ul>
<li>
<p>[修正] 统一错误拦截返回指定为 <code>MediaType=APPLICATION_JSON_UTF8</code> 以解决 <code>Feign</code> 调用解码错误</p>
</li>
</ul>
</div>
<div class="paragraph">
<div class="title">从 <code>1.0.0-RC/betaX</code> 迁移到 <code>1.1.0</code></div>
<p><code>1.1.0</code> 修正了 <code>1.0.0</code> 版本的几个设计缺陷，需要做如下的迁移操作：</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Maven: <code>Dew</code> 框架的版本修正成 <code>1.1.0-X</code>，目前是 <code>1.1.0-beta1</code></p>
</li>
<li>
<p>Maven: <code>boot-core</code> 更名为 <code>boot-starter</code> , <code>cloud-core</code> 更名为 <code>cloud-starter</code></p>
</li>
<li>
<p>核心代码: <code>com.tairanchina.csp.dew.Dew</code> 包路径改成 <code>com.tairanchina.csp.dew.Dew</code></p>
</li>
<li>
<p><code>Dew JDBC</code> 模块（使用MyBatis等其它持久化框架的项目可以忽略）</p>
<div class="ulist">
<ul>
<li>
<p><code>SafeEntity</code> 的创建/更新时间 由 <code>Date</code> 换成了 <code>LocalDateTime</code></p>
</li>
<li>
<p>所有 <code>entity</code> 包 迁移到 <code>com.tairanchina.csp.dew.jdbc.entity</code></p>
</li>
<li>
<p>使用 <code>JdbcTemplate</code> 原生方法时 原来是： <code>Dew.ds().jdbc.xx</code> ，需要修改成 <code>((DewDS)Dew.ds).jdbc.xx</code></p>
</li>
</ul>
</div>
</li>
</ul>
</div>
</div>
<div class="sect4">
<h5 id="1_0_0_rc"><a class="anchor" href="#1_0_0_rc"></a>1.0.0-RC</h5>
<div class="ulist">
<div class="title">Features</div>
<ul>
<li>
<p>[功能]支持新版用户权限中心认证适配(* 新版用户权限中心Release后，此功能代码会有一定变更)</p>
</li>
<li>
<p>[功能]新增SqlBuilder用于快速构建SQL语句</p>
</li>
<li>
<p>[移除]由于 Spring Cloud Thrift RPC 测试不够充分，此版本中暂时移除</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Improvement</div>
<ul>
<li>
<p>[功能]支持rabbit confirm(单条)模式</p>
<div class="literalblock">
<div class="content">
<pre>((RabbitClusterMQ)Dew.cluster.mq).publish(String topic, String message, boolean confirm)
((RabbitClusterMQ)Dew.cluster.mq).request(String address, String message, boolean confirm)</pre>
</div>
</div>
</li>
<li>
<p>[功能]支持 <code>EnabledColumn</code> 结果反转，EnabledColumn用于标识是否启用状态的注解，默认是true是否用，false是禁用，但有些情况下状态字段会使用`del_flag`表示是否删除，这时需要设置结果反转</p>
</li>
<li>
<p>[功能]统一Body及Url Path/Query的异常捕获</p>
</li>
<li>
<p>[功能] <code>tryLock</code> 支持重入</p>
</li>
<li>
<p>[测试]引入 <code>embedded redis</code> 以支持单元测试</p>
</li>
<li>
<p>[文档]添加 以宠物商店为例的 <code>新手入门</code> 章节</p>
</li>
<li>
<p>[修改]原 <code>dew.dao.base-package</code> 改成 <code>dew.jdbc.base-packages</code> 支持多个路径</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Fixed</div>
<ul>
<li>
<p>修正Redis锁 <code>Unlock</code> 处理的线程问题</p>
</li>
<li>
<p>修正jacoco单元测试覆盖率偏少的问题</p>
</li>
</ul>
</div>
</div>
<div class="sect4">
<h5 id="1_0_0_beta5"><a class="anchor" href="#1_0_0_beta5"></a>1.0.0-beta5</h5>
<div class="ulist">
<div class="title">Features</div>
<ul>
<li>
<p>添加服务调用限制（可定义A服务不允许B服务调用，防止服务双向依赖） e.g.</p>
<div class="literalblock">
<div class="content">
<pre>dew.security.exclude-services:
 - serviceB
 - serviceC</pre>
</div>
</div>
</li>
<li>
<p>添加对Thrift的支持</p>
</li>
<li>
<p>支持集群Leader Election（非严格模式）</p>
</li>
<li>
<p>整合Spring Boot Cache</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Improvement</div>
<ul>
<li>
<p>优化CURD脚手架</p>
</li>
<li>
<p>支持UUID形式的主键</p>
</li>
<li>
<p>优化注解自定义查询（ <code>@Select</code> ），通过测试</p>
</li>
<li>
<p>支持自定义异常配置，见 <code>异常处理</code> 章节</p>
</li>
<li>
<p>添加Bean分组校验说明，见 <code>异常处理</code> 章节</p>
</li>
<li>
<p>添加 <code>Sonar</code> 代码质量检查，配置 <code>sonar.host.url</code> 执行 <code>mvn clean verify sonar:sonar</code></p>
</li>
<li>
<p>【需要迁移】使用Druid数据库连接池（注意数据库连接配置变更）</p>
</li>
<li>
<p>【需要迁移】删除 <code>DaoImpl</code> 兼容性类</p>
</li>
<li>
<p>【需要迁移】将 <code>Dew.e</code> 移到 <code>Dew.E.e</code>，添加 <code>Dew.E.checkXX`异常检查方法，见 `异常处理</code> 章节</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Fixed</div>
<ul>
<li>
<p>修正事务失败，重试成功后还是被回滚的问题</p>
</li>
</ul>
</div>
</div>
<div class="sect4">
<h5 id="1_0_0_beta4"><a class="anchor" href="#1_0_0_beta4"></a>1.0.0-beta4</h5>
<div class="ulist">
<div class="title">Features</div>
<ul>
<li>
<p>整合 <code>Spring boot admin</code> 与 <code>Turbine</code>，可直观的监控各个性能及访问指标</p>
</li>
<li>
<p>添加实验功能：使用注解自定义查询（ <code>@Select</code> ）</p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Improvement</div>
<ul>
<li>
<p>添加了几个自定义验证方式</p>
</li>
<li>
<p>添加性能测试报告</p>
</li>
<li>
<p>移除 <code>DaoImpl</code> ，改用接口 <code>DewDao</code></p>
</li>
</ul>
</div>
<div class="admonitionblock warning">
<table>
<tr>
<td class="icon">
<i class="fa icon-warning" title="Warning"></i>
</td>
<td class="content">
为确保兼容， <code>DaoImpl</code> 在这一版本中未物理移除，如有条件请迁移至 <code>DewDao</code>
</td>
</tr>
</table>
</div>
</div>
<div class="sect4">
<h5 id="1_0_0_beta3"><a class="anchor" href="#1_0_0_beta3"></a>1.0.0-beta3</h5>
<div class="olist arabic">
<div class="title">Features</div>
<ol class="arabic">
<li>
<p>Cluster的MQ添加RabbitMQ SPI</p>
</li>
</ol>
</div>
<div class="olist arabic">
<div class="title">Improvement</div>
<ol class="arabic">
<li>
<p>支持自定义http错误码( <code>Dew.e(String code, E ex, StandardCode customHttpCode)</code> )</p>
</li>
<li>
<p>对加了字段校验(@Valid)的对象，如果检验失败会返回错误详细</p>
</li>
<li>
<p>开放将ResultSet转成对象的方法( <code>ds.convertRsToObj(Map&lt;String, Object&gt; rs, Class&lt;E&gt; entityClazz)</code> )</p>
</li>
</ol>
</div>
</div>
<div class="sect4">
<h5 id="1_0_0_beta2"><a class="anchor" href="#1_0_0_beta2"></a>1.0.0-Beta2</h5>
<div class="olist arabic">
<div class="title">Features</div>
<ol class="arabic">
<li>
<p>支持生成Html及PDF版本的离线文档</p>
</li>
</ol>
</div>
<div class="olist arabic">
<div class="title">Improvement</div>
<ol class="arabic">
<li>
<p>添加Dubbo整合示例，提供Dubbo服务提供无法处理`声明式事务`的方案</p>
</li>
<li>
<p>完善文档并改用asciidoc格式</p>
</li>
<li>
<p>统一依赖管理</p>
</li>
<li>
<p><code>parent</code> 中添加公司maven库</p>
</li>
<li>
<p>Hazelcast Client升级到3.8.2</p>
</li>
<li>
<p>Dew-Common升级到1.3.7</p>
</li>
</ol>
</div>
</div>
<div class="sect4">
<h5 id="1_0_0_beta1"><a class="anchor" href="#1_0_0_beta1"></a>1.0.0-beta1</h5>
<div class="olist arabic">
<div class="title">Features</div>
<ol class="arabic">
<li>
<p>多数据源支持，详见说明文档`多数据源支持`章节</p>
</li>
</ol>
</div>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
原`Dew.ds.xx`接口弃用，改为`Dew.ds().xx`，如需要使用其它数据源请使用`Dew.ds(&lt;DS Name&gt;).xx`
</td>
</tr>
</table>
</div>
<div class="olist arabic">
<div class="title">Improvement</div>
<ol class="arabic">
<li>
<p>新增`mybatisplus-example`</p>
</li>
<li>
<p>改善`Swagger`文档支持</p>
</li>
<li>
<p>新增销毁时间支持：<code>boolean tryLock(long waitMillSec, long leaseMillSec)</code></p>
</li>
<li>
<p>锁的等待、销毁时间单位由原来的`秒`改成`毫秒`</p>
</li>
</ol>
</div>
<div class="olist arabic">
<div class="title">Fixed</div>
<ol class="arabic">
<li>
<p>修正`tryLock`锁（`Redis`实现），锁被其它线程或JVM占用时等待时间的计算错误</p>
</li>
</ol>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="路线图"><a class="anchor" href="#路线图"></a>5.3. 路线图</h3>
<div class="ulist">
<ul>
<li>
<p>2.3.0</p>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>支持Java 11</p>
</li>
</ol>
</div>
</li>
<li>
<p>2.2.0</p>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>支持Istio体系</p>
</li>
</ol>
</div>
</li>
<li>
<p>2.1.0</p>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>支持Webflux响应式编程</p>
</li>
</ol>
</div>
</li>
<li>
<p>2.0.0</p>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>升级到Spring Boot 2.x</p>
</li>
<li>
<p>实现兼容Spring Cloud与Istio的部署、监控架构</p>
</li>
<li>
<p>引入DevOps流程</p>
</li>
</ol>
</div>
</li>
<li>
<p>1.5.0</p>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>迁移小泰科技Fork版本到开源版本</p>
</li>
<li>
<p>Review及优化代码去除不常用功能</p>
</li>
</ol>
</div>
</li>
<li>
<p>1.x</p>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>实现核心功能，为公司各项目的研发提供能力支撑</p>
</li>
</ol>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="附录_appendix"><a class="anchor" href="#附录_appendix"></a>6. 附录 Appendix</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="proxies"><a class="anchor" href="#proxies"></a>6.1. 代理选择与设置</h3>
<div class="exampleblock">
<div class="content">
<div class="paragraph">
<p>Kubernetes自身及周边生态对大陆网络的支持不好（当然不是他们的原因），主流的解决方案有两个：</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>使用国内镜像仓库替换，比如 <code>registry.cn-hangzhou.aliyuncs.com/google_containers</code> 下有很多替代的镜像，但不全</p>
</li>
<li>
<p>使用代理，比如 shadowsocks</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>镜像替换的方案一方面非官方镜像会存在安全隐患，另一方面并不是所有镜像都有替换版本，所以我们推荐使用代理的方式。</p>
</div>
</div>
</div>
<div class="paragraph">
<p>市场上有很多提供方案实现网络代理，这里不一一赘述，做为一名国内的程序员拥有自己的代理是标配。</p>
</div>
<div class="paragraph">
<p>下面推荐一个免费稳定的代理方案，一个稳定的方案要求IP随时可切换、机房众多、线路质量高、服务商不会跑路，以此为前提免费的方案只能选择AWS或GCP，只要一张双币信用卡就可以免费使用一年，开通的方式请自行google。</p>
</div>
<div class="paragraph">
<p>代理的技术自然是选择 <code>shadowsocks</code> , 下面以 <code>centos 7.x</code> 为例进行安装设置</p>
</div>
<div class="sect3">
<h4 id="安装配置"><a class="anchor" href="#安装配置"></a>6.1.1. 安装配置</h4>
<div class="listingblock">
<div class="content">
<pre># ---------- Shadowsocks 安装（Centos 7）----------

# 切换到root
sudo -i

curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
python get-pip.py
# 使用官方最新版本
pip install https://github.com/shadowsocks/shadowsocks/archive/master.zip -U
# 可以开多个端口，密码请修改
# method，加密算法选择参见 https://shadowsocks.org/en/spec/AEAD-Ciphers.html
# 不要使用80 8080 808等Web服务常用端口
cat &gt;&gt;/etc/shadowsocks.json &lt;&lt;EOF
{
    "server":"0.0.0.0",
    "port_password": {
        "4456": "123456",
        "6788": "123456"
    },
    "local_port":1080,
    "timeout":600,
    "method":"aes-256-gcm"
}
EOF
cat &gt;&gt;/etc/systemd/system/shadowsocks.service &lt;&lt;EOF
[Unit]
Description=Shadowsocks

[Service]
TimeoutStartSec=0
ExecStart=/usr/bin/ssserver -c /etc/shadowsocks.json

[Install]
WantedBy=multi-user.target
EOF
systemctl enable shadowsocks
systemctl start shadowsocks
systemctl status shadowsocks -l

# ---------- BBR 优化 ----------
# 参考 https://www.vultr.com/docs/how-to-deploy-google-bbr-on-centos-7

sudo -i
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm
yum --enablerepo=elrepo-kernel install kernel-ml -y
egrep ^menuentry /etc/grub2.cfg | cut -f 2 -d \'
# 选择上一步输出的kernel所在行号，从0开始
grub2-set-default 0
# 重启
reboot

sudo -i
echo 'net.core.default_qdisc=fq' | tee -a /etc/sysctl.conf
echo 'net.ipv4.tcp_congestion_control=bbr' | tee -a /etc/sysctl.conf
sysctl -p
sysctl net.ipv4.tcp_available_congestion_control
sysctl -n net.ipv4.tcp_congestion_control
# 看到显示 bbr 即为完成</pre>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="middleware"><a class="anchor" href="#middleware"></a>6.2. 常用中间件安装配置</h3>
<div class="sect3">
<h4 id="postgresql"><a class="anchor" href="#postgresql"></a>6.2.1. PostgreSql</h4>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">wget https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-redhat96-9.6-3.noarch.rpm

rpm -Uvh pgdg-redhat96-9.6-3.noarch.rpm
yum install -y postgresql96-server

/usr/pgsql-9.6/bin/postgresql96-setup initdb

vi /var/lib/pgsql/9.6/data/postgresql.conf
-
listen_addresses='*'
-

vi /var/lib/pgsql/9.6/data/pg_hba.conf
-
host  all  all 0.0.0.0/0 md5
-

systemctl enable postgresql-9.6.service
systemctl start postgresql-9.6.service

su - postgres
psql -U postgres
-
ALTER USER postgres WITH PASSWORD 'Dew!123456';
-</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">HA配置</div>
<div class="content">
<pre># @see https://www.postgresql.org/docs/9.6/high-availability.html</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="redis"><a class="anchor" href="#redis"></a>6.2.2. Redis</h4>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">yum install -y epel-release
yum -y install redis
vi /etc/redis.conf
-
# 注释
# bind 127.0.0.1
# 开启密码
requirepass Dew!123456
-
systemctl start redis</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">HA配置</div>
<div class="content">
<pre># @see https://redislabs.com/redis-features/high-availability</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="mysql"><a class="anchor" href="#mysql"></a>6.2.3. MySQL</h4>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">wget https://repo.mysql.com//mysql57-community-release-el7-11.noarch.rpm
rpm -Uvh mysql57-community-release-el7-11.noarch.rpm
yum install -y mysql-community-server

# 修改编码
vi /etc/my.cnf
-
[client]
default-character-set = utf8

[mysqld]
default-storage-engine = INNODB
character-set-server = utf8
collation-server = utf8_general_ci
-

systemctl start mysqld

# 获取初始密码
grep 'temporary password' /var/log/mysqld.log
mysql -uroot -p &lt;获取到的密码&gt;
-
# 修改密码
ALTER USER 'root'@'localhost' IDENTIFIED BY 'Dew!123456';
# 远程访问(仅测试用)
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'Dew!123456' WITH GRANT OPTION;
exit;
-</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">HA配置</div>
<div class="content">
<pre># @see https://dev.mysql.com/doc/mysql-ha-scalability/en/</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="mongo"><a class="anchor" href="#mongo"></a>6.2.4. Mongo</h4>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">cat &gt;&gt;/etc/yum.repos.d/mongodb-org-4.0.repo &lt;&lt;EOF
[mongodb-org-4.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/\$releasever/mongodb-org/4.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-4.0.asc
EOF

yum install -y mongodb-org

mkdir -p /data/mongo
chown -R mongod:mongod  /data/mongo

mongod --port 27017 --dbpath /data/mongo

# 在另一个终端中执行
mongo --port 27017
-
use admin
# 修改密码
db.createUser(
  {
    user: "dew",
    pwd: "Dew!123456",
    roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ]
  }
)
-

# 支持远程访问
mongod --auth --port 27017 \
    --dbpath /data/mongo \
    --logpath /var/log/mongo \
    --bind_ip 0.0.0.0 \
    --fork</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">HA配置</div>
<div class="content">
<pre># @see https://docs.mongodb.com/manual/core/replica-set-high-availability/</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="gitlab"><a class="anchor" href="#gitlab"></a>6.2.5. Gitlab</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://docs.gitlab.com/omnibus/README.html#installation-and-configuration-using-omnibus-package" class="bare">https://docs.gitlab.com/omnibus/README.html#installation-and-configuration-using-omnibus-package</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash
yum install -y gitlab-ce

# 按需修改，可修改说明见: https://docs.gitlab.com/omnibus/settings/
vi /etc/gitlab/gitlab.rb
-
external_url 'http://gitlab.dew.ms'
...
-
gitlab-ctl reconfigure

# 浏览器访问并修改root密码</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">HA配置</div>
<div class="content">
<pre># @see https://about.gitlab.com/solutions/high-availability/</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="harbor"><a class="anchor" href="#harbor"></a>6.2.6. Harbor</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<a href="https://github.com/goharbor/harbor/blob/master/docs/installation_guide.md" class="bare">https://github.com/goharbor/harbor/blob/master/docs/installation_guide.md</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">镜像存储</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># Harbor安装步骤略过
# 登录Harbor docker login &lt;host&gt; -u &lt;user&gt; -p &lt;password&gt;
docker login harbor.dew.ms -u admin -p Harbor12345
# e.g. 上传镜像
docker pull dewms/devops:latest
docker tag dewms/devops:latest harbor.dew.ms/dewms/devops:latest
docker push harbor.dew.ms/dewms/devops:latest</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="dnsmasq"><a class="anchor" href="#dnsmasq"></a>6.2.7. Dnsmasq</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
dnsmasq为轻量级的DNS解析工具，也可用类似的工具替代。
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">yum install -y dnsmasq
systemctl enable dnsmasq
systemctl start dnsmasq

# 编辑本机的 /etc/hosts 添加映射
-
x.x.x.x gitlab.dew.ms
x.x.x.x config.dew.ms
x.x.x.x harbor.dew.ms
x.x.x.x maven.dew.ms
x.x.x.x minio.dew.ms
x.x.x.x nfs.dew.ms
x.x.x.x es.dew.ms # IP为 group=devops 的任意一台Kubernetes Node节点
x.x.x.x kibana.dew.ms # IP为 group=devops 的任意一台Kubernetes Node节点
x.x.x.x prometheus.dew.ms # IP为 group=devops 的任意一台Kubernetes Node节点
x.x.x.x alertmanager.dew.ms # IP为 group=devops 的任意一台Kubernetes Node节点
x.x.x.x grafana.dew.ms # IP为 group=devops 的任意一台Kubernetes Node节点
x.x.x.x jaeger.dew.ms # IP为 group=devops 的任意一台Kubernetes Node节点
...
-

# 编辑所有容器服务节点，加上dnsmasq节点的IP
vi /etc/resolv.conf
-
nameserver x.x.x.x # IP当前节点 应放在所有nameserver的最上面
-

# TIP: 以上设置在节点重启后可能被重置，更好的做法见：
# @see https://unix.stackexchange.com/questions/163831/nameservers-erased-after-systemctl-restart-network-service
# 方法一：
#   修改/etc/resolv.conf之后，执行
#   sudo chattr +i /etc/resolv.conf
#   可以通过重启网络查看是否被重置
#   service network restart
#   cat /etc/resolv.conf</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="ntpdate"><a class="anchor" href="#ntpdate"></a>6.2.8. NTPDate</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
ntpdate用于服务器间时间同步。
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 在各节点安装
yum install -y ntp
# 修改个节点/etc/crontab文件
echo '*/1 * * * * root ntpdate cn.pool.ntp.org' &gt;&gt; /etc/crontab</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="minio"><a class="anchor" href="#minio"></a>6.2.9. Minio</h4>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">mkdir -p /opt/minio &amp;&amp; cd /opt/minio
wget https://dl.minio.io/server/minio/release/linux-amd64/minio
chmod +x minio
./minio server /mnt/data

# 输出内容示例如下：
# AccessKey: F1HR1NUAPVQVX3UPV73P
# SecretKey: 0+vzU8IK+UjJTepBEiAt9x7QO5k+vYRW2KpISWVs
#
# Browser Access:
#    http://10.200.10.5:9000  http://172.17.0.1:9000 ...

# 再执行
nohup ./minio server /mnt/data &amp;

# 添加域名到客户机hosts并访问 http://minio.dew.ms:9000
# 修改访问AccessKey和SecretKey， e.g. dew / Dew123456
# 创建名为 app-cache 的bucket用于缓存gitlab ci runner(或其它CI/CD服务）的构建缓存</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">HA配置</div>
<div class="content">
<pre># @see https://docs.min.io/docs/distributed-minio-quickstart-guide.html</pre>
</div>
</div>
<div class="listingblock">
<div class="title">多用户配置</div>
<div class="content">
<pre># @see https://docs.min.io/docs/minio-multi-user-quickstart-guide.html</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="nfs"><a class="anchor" href="#nfs"></a>6.2.10. NFS</h4>
<div class="listingblock">
<div class="title">安装配置</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">yum install -y nfs-utils
mkdir -p /data/nfs
chmod 755 /data/nfs

mkdir -p /data/nfs/

vi /etc/exports
-
/data/nfs     *(rw,sync,no_root_squash,no_all_squash)
-

systemctl enable rpcbind
systemctl enable nfs-server
systemctl start rpcbind
systemctl start nfs-server

showmount -e localhost</code></pre>
</div>
</div>
<div class="quoteblock">
<div class="title">常见问题</div>
<blockquote>
<div class="paragraph">
<p>Kubernetes使用NFS做为PV时，报错 <code>kubernetes mount: wrong fs type, bad option, bad superblock</code></p>
</div>
</blockquote>
</div>
<div class="literalblock">
<div class="content">
<pre>各节点执行 yum install -y nfs-utils</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="glusterfs"><a class="anchor" href="#glusterfs"></a>6.2.11. GlusterFS</h4>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
安装见：https://github.com/gluster/gluster-kubernetes/blob/master/docs/setup-guide.md
 <a href="https://github.com/gluster/gluster-kubernetes#quickstart" class="bare">https://github.com/gluster/gluster-kubernetes#quickstart</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="title">使用StorageClass</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash"># 注意在各节点安装 glusterfs-fuse
yum install glusterfs-fuse

# 创建 StorageClass
# @See https://kubernetes.io/docs/concepts/storage/storage-classes/#glusterfs
cat &lt;&lt;EOF | kubectl apply -f -
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: gluster-2replicas-sc
provisioner: kubernetes.io/glusterfs
parameters:
  resturl: "http://gluster.dew.ms"
  restuser: "dew"
  restuserkey: "Dew123456"
  volumetype: "replicate:2"
allowVolumeExpansion: true
EOF
# 创建 PVC
cat &lt;&lt;EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: dew-gluster-pvc
 annotations:
   volume.beta.kubernetes.io/storage-class: gluster-2replicas-sc
spec:
 accessModes:
  - ReadWriteOnce
 resources:
   requests:
     storage: 5Gi
EOF
# 查看创建结果，会发现PV已自动创建
kubectl get pv,pvc</code></pre>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="dew-2-migration-guide"><a class="anchor" href="#dew-2-migration-guide"></a>6.3. Dew 2.x Migration guide[TBD]</h3>
<div class="paragraph">
<p>本手册适用于从原生 Spring Cloud 项目及 Dew 1.x 项目迁移到 Dew 2.x 的操作说明。</p>
</div>
<div class="sect3">
<h4 id="初始准备"><a class="anchor" href="#初始准备"></a>6.3.1. 初始准备</h4>
<div class="ulist checklist">
<ul class="checklist">
<li>
<p><input type="checkbox" data-item-complete="0"> 使用 <code>dew-devops.sh</code> 初始化集群并创建项目</p>
<div class="literalblock">
<div class="content">
<pre>curl -O https://raw.githubusercontent.com/gudaoxuri/dew/master/devops/sh/dew-devops.sh
sh dew-devops.sh
# 选择对应操作，根据提示操作即可
# 选择 1 进行集群初始化
# 选择 2 安装 Gitlab runner
# 选择 3 创建项目，为项目创建namespace、ingress、对应 Harbor 账户和镜像仓库等</pre>
</div>
</div>
</li>
</ul>
</div>
<div id="dew-spring-cloud-config" class="ulist checklist">
<ul class="checklist">
<li>
<p><i class="fa fa-square-o"></i> 安装并设置Spring Cloud Config</p>
<div class="literalblock">
<div class="content">
<pre># 添加helm库
helm repo add helm-dew-spring-cloud-config https://raw.githubusercontent.com/gudaoxuri/dew/master/devops/chart/dew-spring-cloud-config/
# 安装，注意域名要能被Kubernetes Pod访问
helm install helm-dew-spring-cloud-config/dew-spring-cloud-config --name dew-spring-cloud-config --namespace devops \
    --set ingress.hosts={config.dew.ms}
# 修改配置，修改后需要删除Pod以实现重启获取最新配置
kubectl -n devops edit cm custom-config
# e.g.
# spring:
#   cloud:
#     config:
#       server:
#         git:
#           uri: https://gitlab.dew.ms/env/dew.git
#           username: cdman
#           password: cdman123
#   security:
#     user:
#       name: dew
#       password: dew!123456
#
# encrypt:
#   key: dsffjs%^skeSfS@#</pre>
</div>
</div>
</li>
</ul>
</div>
</div>
<div class="sect3">
<h4 id="迁移步骤"><a class="anchor" href="#迁移步骤"></a>6.3.2. 迁移步骤</h4>
<div class="ulist checklist">
<ul class="checklist">
<li>
<p><input type="checkbox" data-item-complete="0"> [Spring Boot] Spring boot 1.x 迁移到 Spring boot 2.x</p>
<div class="literalblock">
<div class="content">
<pre># @see https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide</pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre>######################### 特别注意 ########################</pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre># 关系型数据库连接池默认由Tomcat改为Hikari，对应的配置示例：
spring:
  datasource:
    url: jdbc:mysql://...
    username: ...
    password: "{cipher}..."
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      ...
# MySQL默认的client升级到8.x 注意时区设置及驱动名变更(com.mysql.cj.jdbc.Driver)</pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre># Redis的客户端默认由Jedis改为Lettuce，对应的配置示例：
redis:
  host: ...
  port: 6379
  password: "{cipher}..."
  database: 1
  lettuce:
    pool:
      max-active: 200
      max-wait: 300
      max-idle: 50
      min-idle: 10</pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre># actuators被重构，修改见：https://www.baeldung.com/spring-boot-actuators</pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre># Cache不再支持Guava，替换为Caffeine，见： https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html#boot-features-caching-provider-caffeine</pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre># 配置值不能为空，e.g.
csp:
  sdk:
    basic:
      app-secret: "{cipher}..."
      token-expire-ms:  # 错误，值不能为空</pre>
</div>
</div>
</li>
<li>
<p><input type="checkbox" data-item-complete="0"> [Dew Framework] 修改 <code>dew-parent</code> 的版本号到最新版本</p>
</li>
<li>
<p><input type="checkbox" data-item-complete="0"> [Dew Framework] 使用IDE全局查询替换 <code>com.tairanchina.csp.dew</code> 为 <code>ms.dew</code></p>
</li>
<li>
<p><input type="checkbox" data-item-complete="0"> [Dew Framework] 鉴权修改，详见 <a href="#devops-best-practices-auth">鉴权处理</a></p>
<div class="literalblock">
<div class="content">
<pre>由于Dew 2.x 不推荐引入独立的服务网关，故需要对鉴权做一定的修改 。</pre>
</div>
</div>
</li>
<li>
<p><input type="checkbox" data-item-complete="0"> [Dew DevOps] 删除所有 <code>eureka</code> / <code>zuul</code> 相关的配置</p>
</li>
<li>
<p><input type="checkbox" data-item-complete="0"> [Dew DevOps] 修改项目中的 <code>Spring Cloud Config</code> 配置</p>
<div class="literalblock">
<div class="content">
<pre>spring:
  cloud:
    config:
      uri: # 配置中心的URL（见下文 DevOps实现 说明）, e.g. http://config.dew.ms
      username:  # 配置中心访问用户名
      password:  # 配置中心访问密码
      label:  # 加载的Git分支名</pre>
</div>
</div>
</li>
<li>
<p><input type="checkbox" data-item-complete="0"> [Dew DevOps] 删除或修改 <code>logback-spring.xml</code></p>
<div class="literalblock">
<div class="content">
<pre>日志会由fluentd自动收集，一般情况下无需配置 logback-spring.xml
更改日志级别使用 application-X.yml e.g.
logging:
  level:
    ROOT: INFO
    ms.dew: DEBUG                       # Dew目录日志配置
    org.springframework.jdbc.core: TRACE# Jdbc目录日志配置</pre>
</div>
</div>
</li>
<li>
<p><input type="checkbox" data-item-complete="0"> [Dew DevOps] 为前端工程添加 <code>pom.xml</code> ，e.g.</p>
<div class="literalblock">
<div class="content">
<pre>-----------------
&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
  &lt;parent&gt;
    &lt;groupId&gt;ms.dew&lt;/groupId&gt;
    &lt;artifactId&gt;parent-starter&lt;/artifactId&gt;
    &lt;version&gt;2.0.0-beta2&lt;/version&gt;
  &lt;/parent&gt;
  &lt;groupId&gt;ms.dew.devops.it&lt;/groupId&gt;
  &lt;artifactId&gt;todo-frontend&lt;/artifactId&gt;
  &lt;packaging&gt;pom&lt;/packaging&gt;
  &lt;repositories&gt;
    &lt;repository&gt;
      &lt;id&gt;central&lt;/id&gt;
      &lt;url&gt;https://repo.maven.apache.org/maven2&lt;/url&gt;
    &lt;/repository&gt;
    &lt;repository&gt;
      &lt;id&gt;oss-public&lt;/id&gt;
      &lt;url&gt;https://oss.sonatype.org/content/groups/public&lt;/url&gt;
    &lt;/repository&gt;
    &lt;repository&gt;
      &lt;id&gt;oss-snapshot&lt;/id&gt;
      &lt;url&gt;https://oss.sonatype.org/content/repositories/snapshots&lt;/url&gt;
      &lt;snapshots&gt;
        &lt;enabled&gt;true&lt;/enabled&gt;
        &lt;checksumPolicy&gt;warn&lt;/checksumPolicy&gt;
      &lt;/snapshots&gt;
    &lt;/repository&gt;
  &lt;/repositories&gt;
&lt;/project&gt;
-----------------
将此工程加入到&lt;modules&gt;中</pre>
</div>
</div>
</li>
<li>
<p><input type="checkbox" data-item-complete="0"> [Dew DevOps] 去掉前端编译时的进度条显示</p>
<div class="literalblock">
<div class="content">
<pre>由于进度条的显示调用了清屏子命令，Dew暂无法处理情况，故有诸如 ``webpack -p --progress --hide-modules`` 时应该去掉 ``-p --progress``</pre>
</div>
</div>
</li>
<li>
<p><input type="checkbox" data-item-complete="0"> [Dew DevOps] 为没有继承 <code>parent-starter</code> 的工程添加 <code>parent-starter</code> 模块，以实现DevOps功能，此模块没有任何依赖，故引入不会产生副作用</p>
<div class="literalblock">
<div class="content">
<pre>&lt;parent&gt;
    &lt;groupId&gt;ms.dew&lt;/groupId&gt;
    &lt;artifactId&gt;parent-starter&lt;/artifactId&gt;
    &lt;version&gt;&lt;最新的版本&gt;&lt;/version&gt;
&lt;/parent&gt;</pre>
</div>
</div>
</li>
<li>
<p><input type="checkbox" data-item-complete="0"> [Dew DevOps] 在项目中添加<code>.dew</code>配置，详见 <a href="#devops-configuration-dew">.dew 配置</a></p>
<div class="literalblock">
<div class="content">
<pre># 在根目录添加 .dew ，至少添加 使用到的 profile 及 namespace 信息
# e.g.
# -----------------
# 默认通知配置，详见 Dew的通知处理模块
# 默认为钉钉通知
notify:
  args:
    # 通知的URL，可自行修改，详见 https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq
    url: xxx
profiles:
  test:
    namespace: dew-test
  uat:
    namespace: dew-uat
  prod:
    namespace: dew-prod
# -----------------
# （可选）根据实际情况为每个应用添加 .dew 文件以添加各应用的特殊配置</pre>
</div>
</div>
</li>
<li>
<p><input type="checkbox" data-item-complete="0"> [Dew DevOps] 在项目中添加<code>.gitlab-ci.yml</code>配置，详见 <a href="#devops-cicd-gitlab-template">gitlab-ci-template.yml</a>， e.g.</p>
<div class="literalblock">
<div class="content">
<pre>stages:
  - deploy
cache:
  paths:
    - node_modules/
    - .m2/
# 测试环境部署
test deploy:
  stage: deploy
  only:
    - test
  tags:
    - test
  script:
    - mvn -P devops dew:release
# 用户验收/预发环境部署
uat deploy:
  stage: deploy
  only:
    - uat
  tags:
    - uat
  script:
    - mvn -P devops dew:release
prod deploy: # 生产环境部署
  stage: deploy
  only:
    - prod
  tags:
    - prod
  script:
    - mvn -P devops dew:release</pre>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="sect2">
<h3 id="内置image与chart"><a class="anchor" href="#内置image与chart"></a>6.4. 内置Image与Chart</h3>
<div class="sect3">
<h4 id="dew_docker_image_集成kubectl与helm"><a class="anchor" href="#dew_docker_image_集成kubectl与helm"></a>6.4.1. Dew Docker Image : 集成Kubectl与Helm</h4>
<div class="paragraph">
<p>本镜像为Dew微服务体系的组成部分，集成说明参见： <a href="http://doc.dew.ms/#%E9%83%A8%E7%BD%B2%E8%BF%90%E7%BB%B4_devops_chapter">Dew Devops 部署运维</a></p>
</div>
<div class="listingblock">
<div class="content">
<pre># 直接使用docker hub镜像或手工打包：
docker build -t dewms/k8s-native-client .

# 测试

# 获取当前的kube config
KUBE_CONFIG=`echo $(cat ~/.kube/config | base64) | tr -d " "`
# 进入容器
docker run -it -e KUBE_CONFIG=$KUBE_CONFIG dewms/k8s-native-client
-
# 添加kube config
echo -n $KUBE_CONFIG | base64 -d &gt; $HOME/.kube/config
# 正确显示kubrenetes信息
kubectl version
# 正确显示helm信息（如果存在的话）
helm list
-</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="dew_docker_image_集成ci_cd能力"><a class="anchor" href="#dew_docker_image_集成ci_cd能力"></a>6.4.2. Dew Docker Image : 集成CI/CD能力</h4>
<div class="paragraph">
<p>本镜像为Dew微服务体系的组成部分，集成说明参见： <a href="http://doc.dew.ms/#%E9%83%A8%E7%BD%B2%E8%BF%90%E7%BB%B4_devops_chapter">Dew Devops 部署运维</a></p>
</div>
<div class="listingblock">
<div class="content">
<pre># 直接使用docker hub镜像或手工打包：
docker build -t dewms/devops .
或
docker build -t dewms/devops --build-arg MAVEN_AGENT_URL=&lt;&gt; .



# 测试
docker run -it dewms/devops
-
# 正确显示Java版本
java -version
# 正确显示Maven版本
mvn -version
# 正确显示Node版本
node -v
# 正确显示NPM版本
npm version
-</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="dew_docker_image_spring_cloud_config"><a class="anchor" href="#dew_docker_image_spring_cloud_config"></a>6.4.3. Dew Docker Image : Spring Cloud Config</h4>
<div class="paragraph">
<p>本镜像为Dew微服务体系的组成部分，集成说明参见： <a href="http://doc.dew.ms/#%E9%83%A8%E7%BD%B2%E8%BF%90%E7%BB%B4_devops_chapter">Dew Devops 部署运维</a></p>
</div>
<div class="listingblock">
<div class="content">
<pre># 直接使用docker hub镜像或手工打包：
docker build -t dewms/spring-cloud-config .

# 运行示例
docker run -d --name dew-spring-cloud-config \
    -p 8080:8080 \
    -e spring.profiles.active=prod \
    -v /opt/config:/config \
    dewms/spring-cloud-config</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="helm-chart-spring-cloud-config"><a class="anchor" href="#helm-chart-spring-cloud-config"></a>6.4.4. Dew Helm Chart : Spring Cloud Config</h4>
<div class="paragraph">
<p>本镜像为Dew微服务体系的组成部分，集成说明参见： <a href="http://doc.dew.ms/#%E9%83%A8%E7%BD%B2%E8%BF%90%E7%BB%B4_devops_chapter">Dew Devops 部署运维</a></p>
</div>
<div class="listingblock">
<div class="title">使用</div>
<div class="content">
<pre># 0. 添加库
helm repo add helm-dew-spring-cloud-config https://raw.githubusercontent.com/gudaoxuri/dew/master/devops/chart/dew-spring-cloud-config/

# 1. 安装
helm install helm-dew-spring-cloud-config/dew-spring-cloud-config --name dew-spring-cloud-config --namespace devops \
    --set ingress.hosts={config.dew.ms}

# 2. 测试（Kubernetes集群已安装Ingress controller并暴露80端口）
curl http://dew:override@config.dew.ms/encrypt -d abc

# 3. 修改配置，比如加入git设置
kubectl -n devops edit cm custom-config

# 4. 配置修改后需要删除Pod以实现重启获取最新配置</pre>
</div>
</div>
<div class="listingblock">
<div class="title">发布</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">helm package .
helm repo index .
git add --all</code></pre>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="支持的ci_cd服务"><a class="anchor" href="#支持的ci_cd服务"></a>6.5. 支持的CI/CD服务</h3>
<div class="sect3">
<h4 id="dew-devops-maven-agent"><a class="anchor" href="#dew-devops-maven-agent"></a>6.5.1. Dew DevOps Maven Agent</h4>
<div class="paragraph">
<p>本插件为Dew微服务体系的组成部分，集成说明参见： <a href="http://doc.dew.ms/#%E9%83%A8%E7%BD%B2%E8%BF%90%E7%BB%B4_devops_chapter">Dew Devops 部署运维</a></p>
</div>
<div class="paragraph">
<p>Dew会动态判定应用（子模块）是否需要部署，但Maven无法在启动后动态处理应用是否跳过，
Dew的maven插件为此做了一定要处理，但这一做法只是跳过了执行插件，并没有彻底跳过应用，详见 <a href="#devops-user-manual-release-skip">不需要部署的应用处理</a> 。</p>
</div>
<div class="paragraph">
<p>本插件使用 <code>javaagent</code> 可实现彻底的跳过不需要部署的应用。</p>
</div>
<div class="listingblock">
<div class="title">使用</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-bash" data-lang="bash">SET MAVEN_OPTS=-javaagent:&lt;本插件的jar&gt;
mvn -P devops deploy &lt;其它命令&gt;</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="dew-gitlab-ci"><a class="anchor" href="#dew-gitlab-ci"></a>6.5.2. Dew CI/CD : Gitlab CI 实现</h4>
<div class="paragraph">
<p>此为Gitlab CI上的 CI/CD 处理，集成说明参见： <a href="http://doc.dew.ms/#%E9%83%A8%E7%BD%B2%E8%BF%90%E7%BB%B4_devops_chapter">Dew Devops 部署运维</a></p>
</div>
<div class="olist arabic">
<div class="title">前置准备</div>
<ol class="arabic">
<li>
<p>创建Gitlab项目</p>
</li>
<li>
<p>执行 <code>dew-devops.sh</code> 初始化项目对应的各个环境（详见 <a href="#devops-user-manual">DevOps使用手册</a>）</p>
</li>
<li>
<p>在项目代码中添加并配置 <code>.dew</code> 文件（详见 <a href="#devops-user-manual">DevOps使用手册</a>）</p>
</li>
</ol>
</div>
<div class="olist arabic">
<div class="title">核心流程</div>
<ol class="arabic">
<li>
<p>配置 <code>.gitlab-ci.yml</code>，配置参见 <code>devops/cicd/gilabci/.gitlab-ci-template.yml</code></p>
</li>
<li>
<p>添加 <code>.gitlab-ci.yml</code> 到项目代码根目录</p>
</li>
</ol>
</div>
<div id="devops-cicd-gitlab-template" class="listingblock">
<div class="title">gitlab-ci-template.yml</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-yml" data-lang="yml">#
# Copyright 2019. the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# ======================================================

# 此为 Gitlab CI的模板文件
# 各配置说明见 https://docs.gitlab.com/ee/ci/yaml/

# ======================================================

# 要执行的Stages
# Dew 推荐只创建一个名为deploy的Stages，因为Dew的 devops-maven-plugin 已高度集成了CI/CD流程
stages:
  - deploy

# 缓存目录，必须添加
cache:
  paths:
    # node项目必须添加，需要根据实际情况指定到对应的目录，e.g. frontend/node_modules/
    - node_modules/
    # 所有项目必须添加（因为dew将所有类型的工程都视为maven项目，包含前端工程），固定为此目录
    - .m2/

# 不同环境的部署配置，各环境执行的操作一致，但触发的 branch 及 调用的 Runner 不同

# 测试环境部署
test deploy:
  stage: deploy
  only:
    # 触发构建的 branch 名称
    - test
  tags:
    # 调用的 runner 名称，需要与 Gitlab CI Runner的Tag匹配
    - test
  script:
    # 执行的操作，可以扩展，但要保留以下代码
    - mvn -P devops dew:release
    # 可在此处添加代理
    # e.g. -Dhttp.proxyHost=10.200.4.63 -Dhttp.proxyPort=1080 -Dhttps.proxyHost=10.200.4.63 -Dhttps.proxyPort=1080 -Dhttp.nonProxyHosts="localhost|127.0.0.1|*.dew.ms" -Dhttps.nonProxyHosts="localhost|127.0.0.1|*.dew.ms"

# 用户验收/预发环境部署
uat deploy:
  stage: deploy
  only:
    - uat
  tags:
    - uat
  script:
    - mvn -P devops dew:release

prod deploy: # 生产环境部署
  stage: deploy
  only:
    # 触发构建的 branch 名称
    - prod
  tags:
    # 调用的 runner 名称，需要与 Gitlab CI Runner的Tag匹配
    - prod
  script:
    # 执行的操作，可以扩展，但要保留以下代码
    - mvn -P devops dew:release
    # 可在此处添加代理
    # e.g. -Dhttp.proxyHost=10.200.4.63 -Dhttp.proxyPort=1080 -Dhttps.proxyHost=10.200.4.63 -Dhttps.proxyPort=1080 -Dhttp.nonProxyHosts="localhost|127.0.0.1|*.dew.ms" -Dhttps.nonProxyHosts="localhost|127.0.0.1|*.dew.ms"</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2019-07-17 17:20:12 CST
</div>
</div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.js"></script>
<script>prettyPrint()</script>
</body>
</html>
